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 #---------------------------------------------------------------------------- 
  14 from __future__ 
import division
 
  19 from _oglmisc 
import * 
  26     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  27     global BlackForegroundPen
, NormalFont
 
  29     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  30     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  32     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  33     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  35     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  42 class ShapeTextLine(object): 
  43     def __init__(self
, the_x
, the_y
, the_line
): 
  60     def SetText(self
, text
): 
  68 class ShapeEvtHandler(object): 
  69     def __init__(self
, prev 
= None, shape 
= None): 
  70         self
._previousHandler 
= prev
 
  71         self
._handlerShape 
= shape
 
  76     def SetShape(self
, sh
): 
  77         self
._handlerShape 
= sh
 
  80         return self
._handlerShape
 
  82     def SetPreviousHandler(self
, handler
): 
  83         self
._previousHandler 
= handler
 
  85     def GetPreviousHandler(self
): 
  86         return self
._previousHandler
 
  89         if self
._previousHandler
: 
  90             self
._previousHandler
.OnDraw(dc
) 
  92     def OnMoveLinks(self
, dc
): 
  93         if self
._previousHandler
: 
  94             self
._previousHandler
.OnMoveLinks(dc
) 
  96     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  97         if self
._previousHandler
: 
  98             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
 100     def OnDrawContents(self
, dc
): 
 101         if self
._previousHandler
: 
 102             self
._previousHandler
.OnDrawContents(dc
) 
 104     def OnDrawBranches(self
, dc
, erase 
= False): 
 105         if self
._previousHandler
: 
 106             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 108     def OnSize(self
, x
, y
): 
 109         if self
._previousHandler
: 
 110             self
._previousHandler
.OnSize(x
, y
) 
 112     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 113         if self
._previousHandler
: 
 114             return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
) 
 118     def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 119         if self
._previousHandler
: 
 120             return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
 124     def OnErase(self
, dc
): 
 125         if self
._previousHandler
: 
 126             self
._previousHandler
.OnErase(dc
) 
 128     def OnEraseContents(self
, dc
): 
 129         if self
._previousHandler
: 
 130             self
._previousHandler
.OnEraseContents(dc
) 
 132     def OnHighlight(self
, dc
): 
 133         if self
._previousHandler
: 
 134             self
._previousHandler
.OnHighlight(dc
) 
 136     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 137         if self
._previousHandler
: 
 138             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 140     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 141         if self
._previousHandler
: 
 142             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 144     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 145         if self
._previousHandler
: 
 146             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 148     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 149         if self
._previousHandler
: 
 150             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 152     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 153         if self
._previousHandler
: 
 154             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 156     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 157         if self
._previousHandler
: 
 158             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 160     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 161         if self
._previousHandler
: 
 162             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 164     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 165         if self
._previousHandler
: 
 166             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 168     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 169         if self
._previousHandler
: 
 170             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 172     # Control points ('handles') redirect control to the actual shape, 
 173     # to make it easier to override sizing behaviour. 
 174     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 175         if self
._previousHandler
: 
 176             self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
) 
 178     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 179         if self
._previousHandler
: 
 180             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 182     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 183         if self
._previousHandler
: 
 184             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 186     def OnBeginSize(self
, w
, h
): 
 189     def OnEndSize(self
, w
, h
): 
 192     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 193         if self
._previousHandler
: 
 194             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 196     def OnDrawControlPoints(self
, dc
): 
 197         if self
._previousHandler
: 
 198             self
._previousHandler
.OnDrawControlPoints(dc
) 
 200     def OnEraseControlPoints(self
, dc
): 
 201         if self
._previousHandler
: 
 202             self
._previousHandler
.OnEraseControlPoints(dc
) 
 204     # Can override this to prevent or intercept line reordering. 
 205     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 206         if self
._previousHandler
: 
 207             self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
) 
 211 class Shape(ShapeEvtHandler
): 
 216     The wxShape is the top-level, abstract object that all other objects 
 217     are derived from. All common functionality is represented by wxShape's 
 218     members, and overriden members that appear in derived classes and have 
 219     behaviour as documented for wxShape, are not documented separately. 
 222     GraphicsInSizeToContents 
= False 
 224     def __init__(self
, canvas 
= None): 
 225         ShapeEvtHandler
.__init
__(self
) 
 227         self
._eventHandler 
= self
 
 230         self
._formatted 
= False 
 231         self
._canvas 
= canvas
 
 234         self
._pen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
 235         self
._brush 
= wx
.WHITE_BRUSH
 
 236         self
._font 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
 237         self
._textColour 
= wx
.BLACK
 
 238         self
._textColourName 
= wx
.BLACK
 
 239         self
._visible 
= False 
 240         self
._selected 
= False 
 241         self
._attachmentMode 
= ATTACHMENT_MODE_NONE
 
 242         self
._spaceAttachments 
= True 
 243         self
._disableLabel 
= False 
 244         self
._fixedWidth 
= False 
 245         self
._fixedHeight 
= False 
 246         self
._drawHandles 
= True 
 247         self
._sensitivity 
= OP_ALL
 
 248         self
._draggable 
= True 
 250         self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
 251         self
._shadowMode 
= SHADOW_NONE
 
 252         self
._shadowOffsetX 
= 6 
 253         self
._shadowOffsetY 
= 6 
 254         self
._shadowBrush 
= wx
.BLACK_BRUSH
 
 255         self
._textMarginX 
= 5 
 256         self
._textMarginY 
= 5 
 258         self
._centreResize 
= True 
 259         self
._maintainAspectRatio 
= False 
 260         self
._highlighted 
= False 
 262         self
._branchNeckLength 
= 10 
 263         self
._branchStemLength 
= 10 
 264         self
._branchSpacing 
= 10 
 265         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 269         self
._controlPoints 
= [] 
 270         self
._attachmentPoints 
= [] 
 274         # Set up a default region. Much of the above will be put into 
 275         # the region eventually (the duplication is for compatibility) 
 276         region 
= ShapeRegion() 
 278         region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)) 
 279         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 280         region
.SetColour("BLACK") 
 281         self
._regions
.append(region
) 
 284         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 286     def GetClassName(self
): 
 287         return str(self
.__class
__).split(".")[-1][:-2] 
 291             i 
= self
._parent
.GetChildren().index(self
) 
 292             self
._parent
.GetChildren(i
).remove(self
) 
 296         self
.ClearAttachments() 
 299             self
._canvas
.RemoveShape(self
) 
 301         self
.GetEventHandler().OnDelete() 
 304         """TRUE if the shape may be dragged by the user.""" 
 307     def SetShape(self
, sh
): 
 308         self
._handlerShape 
= sh
 
 311         """Get the internal canvas.""" 
 314     def GetBranchStyle(self
): 
 315         return self
._branchStyle
 
 317     def GetRotation(self
): 
 318         """Return the angle of rotation in radians.""" 
 319         return self
._rotation
 
 321     def SetRotation(self
, rotation
): 
 322         self
._rotation 
= rotation
 
 324     def SetHighlight(self
, hi
, recurse 
= False): 
 325         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 326         self
._highlighted 
= hi
 
 328             for shape 
in self
._children
: 
 329                 shape
.SetHighlight(hi
, recurse
) 
 331     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 332         """Set the shape to be sensitive or insensitive to specific mouse 
 335         sens is a bitlist of the following: 
 341         * OP_ALL (equivalent to a combination of all the above). 
 343         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 345         self
._sensitivity 
= sens
 
 347             for shape 
in self
._children
: 
 348                 shape
.SetSensitivityFilter(sens
, True) 
 350     def SetDraggable(self
, drag
, recursive 
= False): 
 351         """Set the shape to be draggable or not draggable.""" 
 352         self
._draggable 
= drag
 
 354             self
._sensitivity |
= OP_DRAG_LEFT
 
 355         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 356             self
._sensitivity 
-= OP_DRAG_LEFT
 
 359             for shape 
in self
._children
: 
 360                 shape
.SetDraggable(drag
, True) 
 362     def SetDrawHandles(self
, drawH
): 
 363         """Set the drawHandles flag for this shape and all descendants. 
 364         If drawH is TRUE (the default), any handles (control points) will 
 365         be drawn. Otherwise, the handles will not be drawn. 
 367         self
._drawHandles 
= drawH
 
 368         for shape 
in self
._children
: 
 369             shape
.SetDrawHandles(drawH
) 
 371     def SetShadowMode(self
, mode
, redraw 
= False): 
 372         """Set the shadow mode (whether a shadow is drawn or not). 
 373         mode can be one of the following: 
 376           No shadow (the default).  
 378           Shadow on the left side.  
 380           Shadow on the right side. 
 382         if redraw 
and self
.GetCanvas(): 
 383             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 384             self
.GetCanvas().PrepareDC(dc
) 
 386             self
._shadowMode 
= mode
 
 389             self
._shadowMode 
= mode
 
 391     def SetCanvas(self
, theCanvas
): 
 392         """Identical to Shape.Attach.""" 
 393         self
._canvas 
= theCanvas
 
 394         for shape 
in self
._children
: 
 395             shape
.SetCanvas(theCanvas
) 
 397     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 398         """Add the shape to the canvas's shape list. 
 399         If addAfter is non-NULL, will add the shape after this one. 
 401         theCanvas
.AddShape(self
, addAfter
) 
 404         for object in self
._children
: 
 405             object.AddToCanvas(theCanvas
, lastImage
) 
 408     def InsertInCanvas(self
, theCanvas
): 
 409         """Insert the shape at the front of the shape list of canvas.""" 
 410         theCanvas
.InsertShape(self
) 
 413         for object in self
._children
: 
 414             object.AddToCanvas(theCanvas
, lastImage
) 
 417     def RemoveFromCanvas(self
, theCanvas
): 
 418         """Remove the shape from the canvas.""" 
 421         theCanvas
.RemoveShape(self
) 
 422         for object in self
._children
: 
 423             object.RemoveFromCanvas(theCanvas
) 
 425     def ClearAttachments(self
): 
 426         """Clear internal custom attachment point shapes (of class 
 429         self
._attachmentPoints 
= [] 
 431     def ClearText(self
, regionId 
= 0): 
 432         """Clear the text from the specified text region.""" 
 435         if regionId
<len(self
._regions
): 
 436             self
._regions
[regionId
].ClearText() 
 438     def ClearRegions(self
): 
 439         """Clear the ShapeRegions from the shape.""" 
 442     def AddRegion(self
, region
): 
 443         """Add a region to the shape.""" 
 444         self
._regions
.append(region
) 
 446     def SetDefaultRegionSize(self
): 
 447         """Set the default region to be consistent with the shape size.""" 
 448         if not self
._regions
: 
 450         w
, h 
= self
.GetBoundingBoxMax() 
 451         self
._regions
[0].SetSize(w
, h
) 
 453     def HitTest(self
, x
, y
): 
 454         """Given a point on a canvas, returns TRUE if the point was on the 
 455         shape, and returns the nearest attachment point and distance from 
 456         the given point and target. 
 458         width
, height 
= self
.GetBoundingBoxMax() 
 464         width 
+= 4 # Allowance for inaccurate mousing 
 467         left 
= self
._xpos 
- (width 
/ 2) 
 468         top 
= self
._ypos 
- (height 
/ 2) 
 469         right 
= self
._xpos 
+ (width 
/ 2) 
 470         bottom 
= self
._ypos 
+ (height 
/ 2) 
 472         nearest_attachment 
= 0 
 474         # If within the bounding box, check the attachment points 
 476         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 477             n 
= self
.GetNumberOfAttachments() 
 480             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 481             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 484                 e 
= self
.GetAttachmentPositionEdge(i
) 
 487                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 490                         nearest_attachment 
= i
 
 492             return nearest_attachment
, nearest
 
 495     # Format a text string according to the region size, adding 
 496     # strings with positions to region text list 
 498     def FormatText(self
, dc
, s
, i 
= 0): 
 499         """Reformat the given text region; defaults to formatting the 
 504         if not self
._regions
: 
 507         if i
>len(self
._regions
): 
 510         region 
= self
._regions
[i
] 
 511         region
._regionText 
= s
 
 512         dc
.SetFont(region
.GetFont()) 
 514         w
, h 
= region
.GetSize() 
 516         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 518             line 
= ShapeTextLine(0.0, 0.0, s
) 
 519             region
.GetFormattedText().append(line
) 
 523         # Don't try to resize an object with more than one image (this 
 524         # case should be dealt with by overriden handlers) 
 525         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 526            region
.GetFormattedText().GetCount() and \
 
 527            len(self
._regions
) == 1 and \
 
 528            not Shape
.GraphicsInSizeToContents
: 
 530             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 531             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 532                 # If we are a descendant of a composite, must make sure 
 533                 # the composite gets resized properly 
 535                 topAncestor 
= self
.GetTopAncestor() 
 536                 if topAncestor 
!= self
: 
 537                     Shape
.GraphicsInSizeToContents 
= True 
 539                     composite 
= topAncestor
 
 541                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 542                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 543                     composite
.CalculateSize() 
 544                     if composite
.Selected(): 
 545                         composite
.DeleteControlPoints(dc
) 
 546                         composite
.MakeControlPoints() 
 547                         composite
.MakeMandatoryControlPoints() 
 548                     # Where infinite recursion might happen if we didn't stop it 
 550                     Shape
.GraphicsInSizeToContents 
= False 
 554                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 555                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 556                 self
.EraseContents(dc
) 
 557         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 558         self
._formatted 
= True 
 560     def Recentre(self
, dc
): 
 561         """Do recentring (or other formatting) for all the text regions 
 564         w
, h 
= self
.GetBoundingBoxMin() 
 565         for region 
in self
._regions
: 
 566             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 568     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 569         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 570         the shape. Returns False if the line doesn't hit the perimeter. 
 574     def SetPen(self
, the_pen
): 
 575         """Set the pen for drawing the shape's outline.""" 
 578     def SetBrush(self
, the_brush
): 
 579         """Set the brush for filling the shape's shape.""" 
 580         self
._brush 
= the_brush
 
 582     # Get the top - most (non-division) ancestor, or self 
 583     def GetTopAncestor(self
): 
 584         """Return the top-most ancestor of this shape (the root of 
 587         if not self
.GetParent(): 
 590         if isinstance(self
.GetParent(), DivisionShape
): 
 592         return self
.GetParent().GetTopAncestor() 
 595     def SetFont(self
, the_font
, regionId 
= 0): 
 596         """Set the font for the specified text region.""" 
 597         self
._font 
= the_font
 
 598         if regionId
<len(self
._regions
): 
 599             self
._regions
[regionId
].SetFont(the_font
) 
 601     def GetFont(self
, regionId 
= 0): 
 602         """Get the font for the specified text region.""" 
 603         if regionId 
>= len(self
._regions
): 
 605         return self
._regions
[regionId
].GetFont() 
 607     def SetFormatMode(self
, mode
, regionId 
= 0): 
 608         """Set the format mode of the default text region. The argument 
 609         can be a bit list of the following: 
 618         if regionId
<len(self
._regions
): 
 619             self
._regions
[regionId
].SetFormatMode(mode
) 
 621     def GetFormatMode(self
, regionId 
= 0): 
 622         if regionId 
>= len(self
._regions
): 
 624         return self
._regions
[regionId
].GetFormatMode() 
 626     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 627         """Set the colour for the specified text region.""" 
 628         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 629         self
._textColourName 
= the_colour
 
 631         if regionId
<len(self
._regions
): 
 632             self
._regions
[regionId
].SetColour(the_colour
) 
 634     def GetTextColour(self
, regionId 
= 0): 
 635         """Get the colour for the specified text region.""" 
 636         if regionId 
>= len(self
._regions
): 
 638         return self
._regions
[regionId
].GetTextColour() 
 640     def SetRegionName(self
, name
, regionId 
= 0): 
 641         """Set the name for this region. 
 642         The name for a region is unique within the scope of the whole 
 643         composite, whereas a region id is unique only for a single image. 
 645         if regionId
<len(self
._regions
): 
 646             self
._regions
[regionId
].SetName(name
) 
 648     def GetRegionName(self
, regionId 
= 0): 
 649         """Get the region's name. 
 650         A region's name can be used to uniquely determine a region within 
 651         an entire composite image hierarchy. See also Shape.SetRegionName. 
 653         if regionId 
>= len(self
._regions
): 
 655         return self
._regions
[regionId
].GetName() 
 657     def GetRegionId(self
, name
): 
 658         """Get the region's identifier by name. 
 659         This is not unique for within an entire composite, but is unique 
 662         for i
, r 
in enumerate(self
._regions
): 
 663             if r
.GetName() == name
: 
 667     # Name all _regions in all subimages recursively 
 668     def NameRegions(self
, parentName
=""): 
 669         """Make unique names for all the regions in a shape or composite shape.""" 
 670         n 
= self
.GetNumberOfTextRegions() 
 673                 buff 
= parentName
+"."+str(i
) 
 676             self
.SetRegionName(buff
, i
) 
 678         for j
, child 
in enumerate(self
._children
): 
 680                 buff 
= parentName
+"."+str(j
) 
 683             child
.NameRegions(buff
) 
 685     # Get a region by name, possibly looking recursively into composites 
 686     def FindRegion(self
, name
): 
 687         """Find the actual image ('this' if non-composite) and region id 
 688         for the given region name. 
 690         id = self
.GetRegionId(name
) 
 694         for child 
in self
._children
: 
 695             actualImage
, regionId 
= child
.FindRegion(name
) 
 697                 return actualImage
, regionId
 
 701     # Finds all region names for this image (composite or simple). 
 702     def FindRegionNames(self
): 
 703         """Get a list of all region names for this image (composite or simple).""" 
 705         n 
= self
.GetNumberOfTextRegions() 
 707             list.append(self
.GetRegionName(i
)) 
 709         for child 
in self
._children
: 
 710             list += child
.FindRegionNames() 
 714     def AssignNewIds(self
): 
 715         """Assign new ids to this image and its children.""" 
 716         self
._id 
= wx
.NewId() 
 717         for child 
in self
._children
: 
 720     def OnDraw(self
, dc
): 
 723     def OnMoveLinks(self
, dc
): 
 724         # Want to set the ends of all attached links 
 725         # to point to / from this object 
 727         for line 
in self
._lines
: 
 728             line
.GetEventHandler().OnMoveLink(dc
) 
 730     def OnDrawContents(self
, dc
): 
 731         if not self
._regions
: 
 734         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 739         region 
= self
._regions
[0] 
 741             dc
.SetFont(region
.GetFont()) 
 743         dc
.SetTextForeground(region
.GetActualColourObject()) 
 744         dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 745         if not self
._formatted
: 
 746             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 747             self
._formatted 
= True 
 749         if not self
.GetDisableLabel(): 
 750             DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 753     def DrawContents(self
, dc
): 
 754         """Draw the internal graphic of the shape (such as text). 
 756         Do not override this function: override OnDrawContents, which 
 757         is called by this function. 
 759         self
.GetEventHandler().OnDrawContents(dc
) 
 761     def OnSize(self
, x
, y
): 
 764     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 767     def OnErase(self
, dc
): 
 768         if not self
._visible
: 
 772         for line 
in self
._lines
: 
 773             line
.GetEventHandler().OnErase(dc
) 
 775         self
.GetEventHandler().OnEraseContents(dc
) 
 777     def OnEraseContents(self
, dc
): 
 778         if not self
._visible
: 
 781         xp
, yp 
= self
.GetX(), self
.GetY() 
 782         minX
, minY 
= self
.GetBoundingBoxMin() 
 783         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 785         topLeftX 
= xp 
- maxX 
/ 2 - 2 
 786         topLeftY 
= yp 
- maxY 
/ 2 - 2 
 790             penWidth 
= self
._pen
.GetWidth() 
 792         dc
.SetPen(self
.GetBackgroundPen()) 
 793         dc
.SetBrush(self
.GetBackgroundBrush()) 
 795         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 797     def EraseLinks(self
, dc
, attachment
=-1, recurse 
= False): 
 798         """Erase links attached to this shape, but do not repair damage 
 799         caused to other shapes. 
 801         if not self
._visible
: 
 804         for line 
in self
._lines
: 
 805             if attachment
==-1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 806                 line
.GetEventHandler().OnErase(dc
) 
 809             for child 
in self
._children
: 
 810                 child
.EraseLinks(dc
, attachment
, recurse
) 
 812     def DrawLinks(self
, dc
, attachment
=-1, recurse 
= False): 
 813         """Draws any lines linked to this shape.""" 
 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().Draw(dc
) 
 822             for child 
in self
._children
: 
 823                 child
.DrawLinks(dc
, attachment
, recurse
) 
 825     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 826     #  another on an edge of the shape. 
 827     # attachmentPoint is the attachment point (= side) in question. 
 829     # This is the default, rectangular implementation. 
 830     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 831         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 832         that one point comes before another on an edge of the shape. 
 834         attachment is the attachment point (side) in question. 
 836         This function is used in Shape.MoveLineToNewAttachment to determine 
 837         the new line ordering. 
 839         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 840         if physicalAttachment 
in [0, 2]: 
 841             return pt1
.x 
<= pt2
.x
 
 842         elif physicalAttachment 
in [1, 3]: 
 843             return pt1
.y 
<= pt2
.y
 
 847     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 848         """Move the given line (which must already be attached to the shape) 
 849         to a different attachment point on the shape, or a different order 
 850         on the same attachment. 
 852         Calls Shape.AttachmentSortTest and then 
 853         ShapeEvtHandler.OnChangeAttachment. 
 855         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 858         # Is (x, y) on this object? If so, find the new attachment point 
 859         # the user has moved the point to 
 860         hit 
= self
.HitTest(x
, y
) 
 864         newAttachment
, distance 
= hit
 
 868         if to_move
.GetTo() == self
: 
 869             oldAttachment 
= to_move
.GetAttachmentTo() 
 871             oldAttachment 
= to_move
.GetAttachmentFrom() 
 873         # The links in a new ordering 
 874         # First, add all links to the new list 
 875         newOrdering 
= self
._lines
[:] 
 877         # Delete the line object from the list of links; we're going to move 
 878         # it to another position in the list 
 879         del newOrdering
[newOrdering
.index(to_move
)] 
 886         for line 
in newOrdering
: 
 887             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 888                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 889                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 890                 if line
.GetTo() == self
: 
 897                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 898                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 899                 newPoint 
= wx
.RealPoint(x
, y
) 
 901                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 903                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 911             newOrdering
.append(to_move
) 
 913         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 916     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 917         if line
.GetTo() == self
: 
 918             line
.SetAttachmentTo(attachment
) 
 920             line
.SetAttachmentFrom(attachment
) 
 922         self
.ApplyAttachmentOrdering(ordering
) 
 924         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 925         self
.GetCanvas().PrepareDC(dc
) 
 928         if not self
.GetCanvas().GetQuickEditMode(): 
 929             self
.GetCanvas().Redraw(dc
) 
 931     # Reorders the lines according to the given list 
 932     def ApplyAttachmentOrdering(self
, linesToSort
): 
 933         """Apply the line ordering in linesToSort to the shape, to reorder 
 934         the way lines are attached. 
 936         linesStore 
= self
._lines
[:] 
 940         for line 
in linesToSort
: 
 941             if line 
in linesStore
: 
 942                 del linesStore
[linesStore
.index(line
)] 
 943                 self
._lines
.append(line
) 
 945         # Now add any lines that haven't been listed in linesToSort 
 946         self
._lines 
+= linesStore
 
 948     def SortLines(self
, attachment
, linesToSort
): 
 949         """ Reorder the lines coming into the node image at this attachment 
 950         position, in the order in which they appear in linesToSort. 
 952         Any remaining lines not in the list will be added to the end. 
 954         # This is a temporary store of all the lines at this attachment 
 955         # point. We'll tick them off as we've processed them. 
 956         linesAtThisAttachment 
= [] 
 958         for line 
in self
._lines
[:]: 
 959             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 960                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 961                 linesAtThisAttachment
.append(line
) 
 962                 del self
._lines
[self
._lines
.index(line
)] 
 964         for line 
in linesToSort
: 
 965             if line 
in linesAtThisAttachment
: 
 967                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 968                 self
._lines
.Append(line
) 
 970         # Now add any lines that haven't been listed in linesToSort 
 971         self
._lines 
+= linesAtThisAttachment
 
 973     def OnHighlight(self
, dc
): 
 976     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 977         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 979                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 980                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 982     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 983         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
 984             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 985             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 987     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 988         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
 990                 hit 
= self
._parent
.HitTest(x
, y
) 
 992                     attachment
, dist 
= hit
 
 993                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 996         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 997         self
.GetCanvas().PrepareDC(dc
) 
 998         dc
.SetLogicalFunction(OGLRBLF
) 
1000         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1001         dc
.SetPen(dottedPen
) 
1002         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1004         xx 
= x 
+ DragOffsetX
 
1005         yy 
= y 
+ DragOffsetY
 
1007         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1008         w
, h 
= self
.GetBoundingBoxMax() 
1009         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1011     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1012         global DragOffsetX
, DragOffsetY
 
1014         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1016                 hit 
= self
._parent
.HitTest(x
, y
) 
1018                     attachment
, dist 
= hit
 
1019                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1022         DragOffsetX 
= self
._xpos 
- x
 
1023         DragOffsetY 
= self
._ypos 
- y
 
1025         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1026         self
.GetCanvas().PrepareDC(dc
) 
1028         # New policy: don't erase shape until end of drag. 
1030         xx 
= x 
+ DragOffsetX
 
1031         yy 
= y 
+ DragOffsetY
 
1032         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1033         dc
.SetLogicalFunction(OGLRBLF
) 
1035         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1036         dc
.SetPen(dottedPen
) 
1037         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1039         w
, h 
= self
.GetBoundingBoxMax() 
1040         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1041         self
._canvas
.CaptureMouse() 
1043     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1044         if self
._canvas
.HasCapture(): 
1045             self
._canvas
.ReleaseMouse() 
1046         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1048                 hit 
= self
._parent
.HitTest(x
, y
) 
1050                     attachment
, dist 
= hit
 
1051             self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1054         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1055         self
.GetCanvas().PrepareDC(dc
) 
1057         dc
.SetLogicalFunction(wx
.COPY
) 
1058         xx 
= x 
+ DragOffsetX
 
1059         yy 
= y 
+ DragOffsetY
 
1060         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1062         # New policy: erase shape at end of drag. 
1065         self
.Move(dc
, xx
, yy
) 
1066         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1067             self
._canvas
.Redraw(dc
) 
1069     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1070         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1072                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1073                 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1076     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1077         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1079                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1080                 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
) 
1083     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1084         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1086                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1087                 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
) 
1090     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1091         points 
= [[x 
- w 
/ 2, y 
- h 
/ 2], 
1092                 [x 
+ w 
/ 2, y 
- h 
/ 2], 
1093                 [x 
+ w 
/ 2, y 
+ h 
/ 2], 
1094                 [x 
- w 
/ 2, y 
+ h 
/ 2], 
1095                 [x 
- w 
/ 2, y 
- h 
/ 2], 
1098         #dc.DrawLines([[round(x), round(y)] for x, y in points]) 
1099         dc
.DrawLines(points
) 
1101     def Attach(self
, can
): 
1102         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1106         """Disassociates the shape from its canvas.""" 
1109     def Move(self
, dc
, x
, y
, display 
= True): 
1110         """Move the shape to the given position. 
1111         Redraw if display is TRUE. 
1116         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1119         self
._xpos
, self
._ypos 
= x
, y
 
1121         self
.ResetControlPoints() 
1128         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1130     def MoveLinks(self
, dc
): 
1131         """Redraw all the lines attached to the shape.""" 
1132         self
.GetEventHandler().OnMoveLinks(dc
) 
1135         """Draw the whole shape and any lines attached to it. 
1137         Do not override this function: override OnDraw, which is called 
1141             self
.GetEventHandler().OnDraw(dc
) 
1142             self
.GetEventHandler().OnDrawContents(dc
) 
1143             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1144             self
.GetEventHandler().OnDrawBranches(dc
) 
1147         """Flash the shape.""" 
1148         if self
.GetCanvas(): 
1149             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1150             self
.GetCanvas
.PrepareDC(dc
) 
1152             dc
.SetLogicalFunction(OGLRBLF
) 
1154             dc
.SetLogicalFunction(wx
.COPY
) 
1157     def Show(self
, show
): 
1158         """Set a flag indicating whether the shape should be drawn.""" 
1159         self
._visible 
= show
 
1160         for child 
in self
._children
: 
1163     def Erase(self
, dc
): 
1165         Does not repair damage caused to other shapes. 
1167         self
.GetEventHandler().OnErase(dc
) 
1168         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1169         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1171     def EraseContents(self
, dc
): 
1172         """Erase the shape contents, that is, the area within the shape's 
1173         minimum bounding box. 
1175         self
.GetEventHandler().OnEraseContents(dc
) 
1177     def AddText(self
, string
): 
1178         """Add a line of text to the shape's default text region.""" 
1179         if not self
._regions
: 
1182         region 
= self
._regions
[0] 
1184         new_line 
= ShapeTextLine(0, 0, string
) 
1185         text 
= region
.GetFormattedText() 
1186         text
.append(new_line
) 
1188         self
._formatted 
= False 
1190     def SetSize(self
, x
, y
, recursive 
= True): 
1191         """Set the shape's size.""" 
1192         self
.SetAttachmentSize(x
, y
) 
1193         self
.SetDefaultRegionSize() 
1195     def SetAttachmentSize(self
, w
, h
): 
1196         width
, height 
= self
.GetBoundingBoxMin() 
1200             scaleX 
= long(w
) / width
 
1204             scaleY 
= long(h
) / height
 
1206         for point 
in self
._attachmentPoints
: 
1207             point
._x 
= point
._x 
* scaleX
 
1208             point
._y 
= point
._y 
* scaleY
 
1210     # Add line FROM this object 
1211     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom
=-1, positionTo
=-1): 
1212         """Add a line between this shape and the given other shape, at the 
1213         specified attachment points. 
1215         The position in the list of lines at each end can also be specified, 
1216         so that the line will be drawn at a particular point on its attachment 
1219         if positionFrom
==-1: 
1220             if not line 
in self
._lines
: 
1221                 self
._lines
.append(line
) 
1223             # Don't preserve old ordering if we have new ordering instructions 
1225                 self
._lines
.remove(line
) 
1228             if positionFrom
<len(self
._lines
): 
1229                 self
._lines
.insert(positionFrom
, line
) 
1231                 self
._lines
.append(line
) 
1234             if not other 
in other
._lines
: 
1235                 other
._lines
.append(line
) 
1237             # Don't preserve old ordering if we have new ordering instructions 
1239                 other
._lines
.remove(line
) 
1242             if positionTo
<len(other
._lines
): 
1243                 other
._lines
.insert(positionTo
, line
) 
1245                 other
._lines
.append(line
) 
1249         line
.SetAttachments(attachFrom
, attachTo
) 
1251         dc 
= wx
.ClientDC(self
._canvas
) 
1252         self
._canvas
.PrepareDC(dc
) 
1255     def RemoveLine(self
, line
): 
1256         """Remove the given line from the shape's list of attached lines.""" 
1257         if line
.GetFrom() == self
: 
1258             line
.GetTo()._lines
.remove(line
) 
1260             line
.GetFrom()._lines
.remove(line
) 
1262         self
._lines
.remove(line
) 
1264     # Default - make 6 control points 
1265     def MakeControlPoints(self
): 
1266         """Make a list of control points (draggable handles) appropriate to 
1269         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1270         minX
, minY 
= self
.GetBoundingBoxMin() 
1272         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1273         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1275         # Offsets from main object 
1277         bottom 
= heightMin 
/ 2 + (maxY 
- minY
) 
1279         right 
= widthMin 
/ 2 + (maxX 
- minX
) 
1281         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1282         self
._canvas
.AddShape(control
) 
1283         self
._controlPoints
.append(control
) 
1285         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1286         self
._canvas
.AddShape(control
) 
1287         self
._controlPoints
.append(control
) 
1289         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1290         self
._canvas
.AddShape(control
) 
1291         self
._controlPoints
.append(control
) 
1293         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1294         self
._canvas
.AddShape(control
) 
1295         self
._controlPoints
.append(control
) 
1297         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1298         self
._canvas
.AddShape(control
) 
1299         self
._controlPoints
.append(control
) 
1301         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1302         self
._canvas
.AddShape(control
) 
1303         self
._controlPoints
.append(control
) 
1305         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1306         self
._canvas
.AddShape(control
) 
1307         self
._controlPoints
.append(control
) 
1309         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1310         self
._canvas
.AddShape(control
) 
1311         self
._controlPoints
.append(control
) 
1313     def MakeMandatoryControlPoints(self
): 
1314         """Make the mandatory control points. 
1316         For example, the control point on a dividing line should appear even 
1317         if the divided rectangle shape's handles should not appear (because 
1318         it is the child of a composite, and children are not resizable). 
1320         for child 
in self
._children
: 
1321             child
.MakeMandatoryControlPoints() 
1323     def ResetMandatoryControlPoints(self
): 
1324         """Reset the mandatory control points.""" 
1325         for child 
in self
._children
: 
1326             child
.ResetMandatoryControlPoints() 
1328     def ResetControlPoints(self
): 
1329         """Reset the positions of the control points (for instance when the 
1330         shape's shape has changed). 
1332         self
.ResetMandatoryControlPoints() 
1334         if len(self
._controlPoints
) == 0: 
1337         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1338         minX
, minY 
= self
.GetBoundingBoxMin() 
1340         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1341         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1343         # Offsets from main object 
1345         bottom 
= heightMin 
/ 2 + (maxY 
- minY
) 
1347         right 
= widthMin 
/ 2 + (maxX 
- minX
) 
1349         self
._controlPoints
[0]._xoffset 
= left
 
1350         self
._controlPoints
[0]._yoffset 
= top
 
1352         self
._controlPoints
[1]._xoffset 
= 0 
1353         self
._controlPoints
[1]._yoffset 
= top
 
1355         self
._controlPoints
[2]._xoffset 
= right
 
1356         self
._controlPoints
[2]._yoffset 
= top
 
1358         self
._controlPoints
[3]._xoffset 
= right
 
1359         self
._controlPoints
[3]._yoffset 
= 0 
1361         self
._controlPoints
[4]._xoffset 
= right
 
1362         self
._controlPoints
[4]._yoffset 
= bottom
 
1364         self
._controlPoints
[5]._xoffset 
= 0 
1365         self
._controlPoints
[5]._yoffset 
= bottom
 
1367         self
._controlPoints
[6]._xoffset 
= left
 
1368         self
._controlPoints
[6]._yoffset 
= bottom
 
1370         self
._controlPoints
[7]._xoffset 
= left
 
1371         self
._controlPoints
[7]._yoffset 
= 0 
1373     def DeleteControlPoints(self
, dc 
= None): 
1374         """Delete the control points (or handles) for the shape. 
1376         Does not redraw the shape. 
1378         for control 
in self
._controlPoints
: 
1380                 control
.GetEventHandler().OnErase(dc
) 
1381             self
._canvas
.RemoveShape(control
) 
1383         self
._controlPoints 
= [] 
1385         # Children of divisions are contained objects, 
1387         if not isinstance(self
, DivisionShape
): 
1388             for child 
in self
._children
: 
1389                 child
.DeleteControlPoints(dc
) 
1391     def OnDrawControlPoints(self
, dc
): 
1392         if not self
._drawHandles
: 
1395         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1396         dc
.SetPen(wx
.BLACK_PEN
) 
1398         for control 
in self
._controlPoints
: 
1401         # Children of divisions are contained objects, 
1403         # This test bypasses the type facility for speed 
1404         # (critical when drawing) 
1406         if not isinstance(self
, DivisionShape
): 
1407             for child 
in self
._children
: 
1408                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1410     def OnEraseControlPoints(self
, dc
): 
1411         for control 
in self
._controlPoints
: 
1414         if not isinstance(self
, DivisionShape
): 
1415             for child 
in self
._children
: 
1416                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1418     def Select(self
, select
, dc 
= None): 
1419         """Select or deselect the given shape, drawing or erasing control points 
1420         (handles) as necessary. 
1422         self
._selected 
= select
 
1424             self
.MakeControlPoints() 
1425             # Children of divisions are contained objects, 
1427             if not isinstance(self
, DivisionShape
): 
1428                 for child 
in self
._children
: 
1429                     child
.MakeMandatoryControlPoints() 
1431                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1433             self
.DeleteControlPoints(dc
) 
1434             if not isinstance(self
, DivisionShape
): 
1435                 for child 
in self
._children
: 
1436                     child
.DeleteControlPoints(dc
) 
1439         """TRUE if the shape is currently selected.""" 
1440         return self
._selected
 
1442     def AncestorSelected(self
): 
1443         """TRUE if the shape's ancestor is currently selected.""" 
1446         if not self
.GetParent(): 
1448         return self
.GetParent().AncestorSelected() 
1450     def GetNumberOfAttachments(self
): 
1451         """Get the number of attachment points for this shape.""" 
1452         # Should return the MAXIMUM attachment point id here, 
1453         # so higher-level functions can iterate through all attachments, 
1454         # even if they're not contiguous. 
1456         if len(self
._attachmentPoints
) == 0: 
1460             for point 
in self
._attachmentPoints
: 
1465     def AttachmentIsValid(self
, attachment
): 
1466         """TRUE if attachment is a valid attachment point.""" 
1467         if len(self
._attachmentPoints
) == 0: 
1468             return attachment 
in range(4) 
1470         for point 
in self
._attachmentPoints
: 
1471             if point
._id 
== attachment
: 
1475     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1476         """Get the position at which the given attachment point should be drawn. 
1478         If attachment isn't found among the attachment points of the shape, 
1481         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1482             return self
._xpos
, self
._ypos
 
1483         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1484             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1486         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1487             if len(self
._attachmentPoints
): 
1488                 for point 
in self
._attachmentPoints
: 
1489                     if point
._id 
== attachment
: 
1490                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1493                 # Assume is rectangular 
1494                 w
, h 
= self
.GetBoundingBoxMax() 
1495                 top 
= self
._ypos 
+ h 
/ 2 
1496                 bottom 
= self
._ypos 
- h 
/ 2 
1497                 left 
= self
._xpos 
- w 
/ 2 
1498                 right 
= self
._xpos 
+ w 
/ 2 
1501                 line 
and line
.IsEnd(self
) 
1503                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1506                 if physicalAttachment 
== 0: 
1507                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1508                 elif physicalAttachment 
== 1: 
1509                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1510                 elif physicalAttachment 
== 2: 
1511                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1512                 elif physicalAttachment 
== 3: 
1513                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1519     def GetBoundingBoxMax(self
): 
1520         """Get the maximum bounding box for the shape, taking into account 
1521         external features such as shadows. 
1523         ww
, hh 
= self
.GetBoundingBoxMin() 
1524         if self
._shadowMode 
!= SHADOW_NONE
: 
1525             ww 
+= self
._shadowOffsetX
 
1526             hh 
+= self
._shadowOffsetY
 
1529     def GetBoundingBoxMin(self
): 
1530         """Get the minimum bounding box for the shape, that defines the area 
1531         available for drawing the contents (such as text). 
1537     def HasDescendant(self
, image
): 
1538         """TRUE if image is a descendant of this composite.""" 
1541         for child 
in self
._children
: 
1542             if child
.HasDescendant(image
): 
1546     # Clears points from a list of wxRealPoints, and clears list 
1547     # Useless in python? /pi 
1548     def ClearPointList(self
, list): 
1551     # Assuming the attachment lies along a vertical or horizontal line, 
1552     # calculate the position on that point. 
1553     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1554         """Assuming the attachment lies along a vertical or horizontal line, 
1555         calculate the position on that point. 
1560             The first point of the line repesenting the edge of the shape. 
1563             The second point of the line representing the edge of the shape. 
1566             The position on the edge (for example there may be 6 lines at 
1567             this attachment point, and this may be the 2nd line. 
1570             The number of lines at this edge. 
1577         This function expects the line to be either vertical or horizontal, 
1578         and determines which. 
1580         isEnd 
= line 
and line
.IsEnd(self
) 
1582         # Are we horizontal or vertical? 
1583         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1593             if self
._spaceAttachments
: 
1594                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1595                     # Align line according to the next handle along 
1596                     point 
= line
.GetNextControlPoint(self
) 
1597                     if point
[0]<firstPoint
[0]: 
1599                     elif point
[0]>secondPoint
[0]: 
1604                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1) 
1606                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2 # Midpoint 
1609             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1618             if self
._spaceAttachments
: 
1619                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1620                     # Align line according to the next handle along 
1621                     point 
= line
.GetNextControlPoint(self
) 
1622                     if point
[1]<firstPoint
[1]: 
1624                     elif point
[1]>secondPoint
[1]: 
1629                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1) 
1631                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2 # Midpoint 
1636     # Return the zero-based position in m_lines of line 
1637     def GetLinePosition(self
, line
): 
1638         """Get the zero-based position of line in the list of lines 
1642             return self
._lines
.index(line
) 
1650     # shoulder1 ->---------<- shoulder2 
1652     #                      <- branching attachment point N-1 
1654     def GetBranchingAttachmentInfo(self
, attachment
): 
1655         """Get information about where branching connections go. 
1657         Returns FALSE if there are no lines at this attachment. 
1659         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1661         # Number of lines at this attachment 
1662         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1667         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1668         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1670         neck 
= wx
.RealPoint() 
1671         shoulder1 
= wx
.RealPoint() 
1672         shoulder2 
= wx
.RealPoint() 
1674         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1675         if physicalAttachment 
== 0: 
1676             neck
.x 
= self
.GetX() 
1677             neck
.y 
= root
.y 
- self
._branchNeckLength
 
1679             shoulder1
.x 
= root
.x 
- totalBranchLength 
/ 2 
1680             shoulder2
.x 
= root
.x 
+ totalBranchLength 
/ 2 
1682             shoulder1
.y 
= neck
.y
 
1683             shoulder2
.y 
= neck
.y
 
1684         elif physicalAttachment 
== 1: 
1685             neck
.x 
= root
.x 
+ self
._branchNeckLength
 
1688             shoulder1
.x 
= neck
.x
 
1689             shoulder2
.x 
= neck
.x
 
1691             shoulder1
.y 
= neck
.y 
- totalBranchLength 
/ 2 
1692             shoulder1
.y 
= neck
.y 
+ totalBranchLength 
/ 2 
1693         elif physicalAttachment 
== 2: 
1694             neck
.x 
= self
.GetX() 
1695             neck
.y 
= root
.y 
+ self
._branchNeckLength
 
1697             shoulder1
.x 
= root
.x 
- totalBranchLength 
/ 2 
1698             shoulder2
.x 
= root
.x 
+ totalBranchLength 
/ 2 
1700             shoulder1
.y 
= neck
.y
 
1701             shoulder2
.y 
= neck
.y
 
1702         elif physicalAttachment 
== 3: 
1703             neck
.x 
= root
.x 
- self
._branchNeckLength
 
1706             shoulder1
.x 
= neck
.x
 
1707             shoulder2
.x 
= neck
.x
 
1709             shoulder1
.y 
= neck
.y 
- totalBranchLength 
/ 2 
1710             shoulder2
.y 
= neck
.y 
+ totalBranchLength 
/ 2 
1712             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1713         return root
, neck
, shoulder1
, shoulder2
 
1715     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1716         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1718         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1720         stemPt 
= wx
.RealPoint() 
1722         if physicalAttachment 
== 0: 
1723             pt
.y 
= neck
.y 
- self
._branchStemLength
 
1724             pt
.x 
= shoulder1
.x 
+ n 
* self
._branchSpacing
 
1728         elif physicalAttachment 
== 2: 
1729             pt
.y 
= neck
.y 
+ self
._branchStemLength
 
1730             pt
.x 
= shoulder1
.x 
+ n 
* self
._branchStemLength
 
1734         elif physicalAttachment 
== 1: 
1735             pt
.x 
= neck
.x 
+ self
._branchStemLength
 
1736             pt
.y 
= shoulder1
.y 
+ n 
* self
._branchSpacing
 
1740         elif physicalAttachment 
== 3: 
1741             pt
.x 
= neck
.x 
- self
._branchStemLength
 
1742             pt
.y 
= shoulder1
.y 
+ n 
* self
._branchSpacing
 
1747             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1751     def GetAttachmentLineCount(self
, attachment
): 
1752         """Get the number of lines at this attachment position.""" 
1754         for lineShape 
in self
._lines
: 
1755             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1757             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1761     def GetBranchingAttachmentRoot(self
, attachment
): 
1762         """Get the root point at the given attachment.""" 
1763         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1765         root 
= wx
.RealPoint() 
1767         width
, height 
= self
.GetBoundingBoxMax() 
1769         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1770         if physicalAttachment 
== 0: 
1771             root
.x 
= self
.GetX() 
1772             root
.y 
= self
.GetY() - height 
/ 2 
1773         elif physicalAttachment 
== 1: 
1774             root
.x 
= self
.GetX() + width 
/ 2 
1775             root
.y 
= self
.GetY() 
1776         elif physicalAttachment 
== 2: 
1777             root
.x 
= self
.GetX() 
1778             root
.y 
= self
.GetY() + height 
/ 2 
1779         elif physicalAttachment 
== 3: 
1780             root
.x 
= self
.GetX() - width 
/ 2 
1781             root
.y 
= self
.GetY() 
1783             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1787     # Draw or erase the branches (not the actual arcs though) 
1788     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1789         count 
= self
.GetAttachmentLineCount(attachment
) 
1793         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1796             dc
.SetPen(wx
.WHITE_PEN
) 
1797             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1799             dc
.SetPen(wx
.BLACK_PEN
) 
1800             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1803         dc
.DrawLine(root
, neck
) 
1806             # Draw shoulder-to-shoulder line 
1807             dc
.DrawLine(shoulder1
, shoulder2
) 
1808         # Draw all the little branches 
1809         for i 
in range(count
): 
1810             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1811             dc
.DrawLine(stemPt
, pt
) 
1813             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count
>1: 
1815                 dc
.DrawEllipse(stemPt
.x 
- blobSize 
/ 2, stemPt
.y 
- blobSize 
/ 2, blobSize
, blobSize
) 
1817     def OnDrawBranches(self
, dc
, erase 
= False): 
1818         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1820         for i 
in range(self
.GetNumberOfAttachments()): 
1821             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1823     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1824         """ Only get the attachment position at the _edge_ of the shape, 
1825         ignoring branching mode. This is used e.g. to indicate the edge of 
1826         interest, not the point on the attachment branch. 
1828         oldMode 
= self
._attachmentMode
 
1830         # Calculate as if to edge, not branch 
1831         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1832             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1833         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1834         self
._attachmentMode 
= oldMode
 
1838     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1839         """ Rotate the standard attachment point from physical 
1840         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1842         if RoughlyEqual(self
.GetRotation(), 0): 
1843             i 
= physicalAttachment
 
1844         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2): 
1845             i 
= physicalAttachment 
- 1 
1846         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1847             i 
= physicalAttachment 
- 2 
1848         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2): 
1849             i 
= physicalAttachment 
- 3 
1851             # Can't handle -- assume the same 
1852             return physicalAttachment
 
1859     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1860         """Rotate the standard attachment point from logical 
1861         to physical (0 is always North). 
1863         if RoughlyEqual(self
.GetRotation(), 0): 
1864             i 
= logicalAttachment
 
1865         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2): 
1866             i 
= logicalAttachment 
+ 1 
1867         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1868             i 
= logicalAttachment 
+ 2 
1869         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2): 
1870             i 
= logicalAttachment 
+ 3 
1872             return logicalAttachment
 
1879     def Rotate(self
, x
, y
, theta
): 
1880         """Rotate about the given axis by the given amount in radians.""" 
1881         self
._rotation 
= theta
 
1882         if self
._rotation
<0: 
1883             self
._rotation 
+= 2 * math
.pi
 
1884         elif self
._rotation
>2 * math
.pi
: 
1885             self
._rotation 
-= 2 * math
.pi
 
1887     def GetBackgroundPen(self
): 
1888         """Return pen of the right colour for the background.""" 
1889         if self
.GetCanvas(): 
1890             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1891         return WhiteBackgroundPen
 
1893     def GetBackgroundBrush(self
): 
1894         """Return brush of the right colour for the background.""" 
1895         if self
.GetCanvas(): 
1896             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1897         return WhiteBackgroundBrush
 
1900         """Get the x position of the centre of the shape.""" 
1904         """Get the y position of the centre of the shape.""" 
1908         """Set the x position of the shape.""" 
1912         """Set the y position of the shape.""" 
1915     def GetParent(self
): 
1916         """Return the parent of this shape, if it is part of a composite.""" 
1919     def SetParent(self
, p
): 
1922     def GetChildren(self
): 
1923         """Return the list of children for this shape.""" 
1924         return self
._children
 
1926     def GetDrawHandles(self
): 
1927         """Return the list of drawhandles.""" 
1928         return self
._drawHandles
 
1930     def GetEventHandler(self
): 
1931         """Return the event handler for this shape.""" 
1932         return self
._eventHandler
 
1934     def SetEventHandler(self
, handler
): 
1935         """Set the event handler for this shape.""" 
1936         self
._eventHandler 
= handler
 
1938     def Recompute(self
): 
1939         """Recomputes any constraints associated with the shape. 
1941         Normally applicable to CompositeShapes only, but harmless for 
1942         other classes of Shape. 
1946     def IsHighlighted(self
): 
1947         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1948         return self
._highlighted
 
1950     def GetSensitivityFilter(self
): 
1951         """Return the sensitivity filter, a bitlist of values. 
1953         See Shape.SetSensitivityFilter. 
1955         return self
._sensitivity
 
1957     def SetFixedSize(self
, x
, y
): 
1958         """Set the shape to be fixed size.""" 
1959         self
._fixedWidth 
= x
 
1960         self
._fixedHeight 
= y
 
1962     def GetFixedSize(self
): 
1963         """Return flags indicating whether the shape is of fixed size in 
1966         return self
._fixedWidth
, self
._fixedHeight
 
1968     def GetFixedWidth(self
): 
1969         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1970         return self
._fixedWidth
 
1972     def GetFixedHeight(self
): 
1973         """TRUE if the shape cannot be resized in the vertical plane.""" 
1974         return self
._fixedHeight
 
1976     def SetSpaceAttachments(self
, sp
): 
1977         """Indicate whether lines should be spaced out evenly at the point 
1978         they touch the node (sp = True), or whether they should join at a single 
1981         self
._spaceAttachments 
= sp
 
1983     def GetSpaceAttachments(self
): 
1984         """Return whether lines should be spaced out evenly at the point they 
1985         touch the node (True), or whether they should join at a single point 
1988         return self
._spaceAttachments
 
1990     def SetCentreResize(self
, cr
): 
1991         """Specify whether the shape is to be resized from the centre (the 
1992         centre stands still) or from the corner or side being dragged (the 
1993         other corner or side stands still). 
1995         self
._centreResize 
= cr
 
1997     def GetCentreResize(self
): 
1998         """TRUE if the shape is to be resized from the centre (the centre stands 
1999         still), or FALSE if from the corner or side being dragged (the other 
2000         corner or side stands still) 
2002         return self
._centreResize
 
2004     def SetMaintainAspectRatio(self
, ar
): 
2005         """Set whether a shape that resizes should not change the aspect ratio 
2006         (width and height should be in the original proportion). 
2008         self
._maintainAspectRatio 
= ar
 
2010     def GetMaintainAspectRatio(self
): 
2011         """TRUE if shape keeps aspect ratio during resize.""" 
2012         return self
._maintainAspectRatio
 
2015         """Return the list of lines connected to this shape.""" 
2018     def SetDisableLabel(self
, flag
): 
2019         """Set flag to TRUE to stop the default region being shown.""" 
2020         self
._disableLabel 
= flag
 
2022     def GetDisableLabel(self
): 
2023         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2024         return self
._disableLabel
 
2026     def SetAttachmentMode(self
, mode
): 
2027         """Set the attachment mode. 
2029         If TRUE, attachment points will be significant when drawing lines to 
2030         and from this shape. 
2031         If FALSE, lines will be drawn as if to the centre of the shape. 
2033         self
._attachmentMode 
= mode
 
2035     def GetAttachmentMode(self
): 
2036         """Return the attachment mode. 
2038         See Shape.SetAttachmentMode. 
2040         return self
._attachmentMode
 
2043         """Set the integer identifier for this shape.""" 
2047         """Return the integer identifier for this shape.""" 
2051         """TRUE if the shape is in a visible state, FALSE otherwise. 
2053         Note that this has nothing to do with whether the window is hidden 
2054         or the shape has scrolled off the canvas; it refers to the internal 
2057         return self
._visible
 
2060         """Return the pen used for drawing the shape's outline.""" 
2064         """Return the brush used for filling the shape.""" 
2067     def GetNumberOfTextRegions(self
): 
2068         """Return the number of text regions for this shape.""" 
2069         return len(self
._regions
) 
2071     def GetRegions(self
): 
2072         """Return the list of ShapeRegions.""" 
2073         return self
._regions
 
2075     # Control points ('handles') redirect control to the actual shape, to 
2076     # make it easier to override sizing behaviour. 
2077     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2078         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2080         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2081         self
.GetCanvas().PrepareDC(dc
) 
2083         dc
.SetLogicalFunction(OGLRBLF
) 
2085         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2086         dc
.SetPen(dottedPen
) 
2087         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2089         if self
.GetCentreResize(): 
2090             # Maintain the same centre point 
2091             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2092             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2094             # Constrain sizing according to what control point you're dragging 
2095             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2096                 if self
.GetMaintainAspectRatio(): 
2097                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2099                     new_height 
= bound_y
 
2100             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2101                 if self
.GetMaintainAspectRatio(): 
2102                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2105             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2106                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2108             if self
.GetFixedWidth(): 
2111             if self
.GetFixedHeight(): 
2112                 new_height 
= bound_y
 
2114             pt
._controlPointDragEndWidth 
= new_width
 
2115             pt
._controlPointDragEndHeight 
= new_height
 
2117             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2119             # Don't maintain the same centre point 
2120             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2121             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2122             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2123             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2124             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2125                 newY1 
= pt
._controlPointDragStartY
 
2126                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2127             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2128                 newX1 
= pt
._controlPointDragStartX
 
2129                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2130             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2131                 newH 
= (newX2 
- newX1
) * (pt
._controlPointDragStartHeight 
/ pt
._controlPointDragStartWidth
) 
2132                 if self
.GetY()>pt
._controlPointDragStartY
: 
2133                     newY2 
= newY1 
+ newH
 
2135                     newY1 
= newY2 
- newH
 
2137             newWidth 
= newX2 
- newX1
 
2138             newHeight 
= newY2 
- newY1
 
2140             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2141                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2143             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2144                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2146             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2 
2147             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2 
2148             if self
.GetFixedWidth(): 
2151             if self
.GetFixedHeight(): 
2154             pt
._controlPointDragEndWidth 
= newWidth
 
2155             pt
._controlPointDragEndHeight 
= newHeight
 
2156             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2158     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2159         self
._canvas
.CaptureMouse() 
2161         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2162         self
.GetCanvas().PrepareDC(dc
) 
2164         dc
.SetLogicalFunction(OGLRBLF
) 
2166         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2167         self
.GetEventHandler().OnEndSize(bound_x
, bound_y
) 
2169         # Choose the 'opposite corner' of the object as the stationary 
2170         # point in case this is non-centring resizing. 
2171         if pt
.GetX()<self
.GetX(): 
2172             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2 
2174             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2 
2176         if pt
.GetY()<self
.GetY(): 
2177             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2 
2179             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2 
2181         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2182             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2 
2183         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2184             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2 
2186         # We may require the old width and height 
2187         pt
._controlPointDragStartWidth 
= bound_x
 
2188         pt
._controlPointDragStartHeight 
= bound_y
 
2190         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2191         dc
.SetPen(dottedPen
) 
2192         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2194         if self
.GetCentreResize(): 
2195             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2196             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2198             # Constrain sizing according to what control point you're dragging 
2199             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2200                 if self
.GetMaintainAspectRatio(): 
2201                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2203                     new_height 
= bound_y
 
2204             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2205                 if self
.GetMaintainAspectRatio(): 
2206                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2209             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2210                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2212             if self
.GetFixedWidth(): 
2215             if self
.GetFixedHeight(): 
2216                 new_height 
= bound_y
 
2218             pt
._controlPointDragEndWidth 
= new_width
 
2219             pt
._controlPointDragEndHeight 
= new_height
 
2220             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2222             # Don't maintain the same centre point 
2223             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2224             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2225             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2226             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2227             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2228                 newY1 
= pt
._controlPointDragStartY
 
2229                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2230             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2231                 newX1 
= pt
._controlPointDragStartX
 
2232                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2233             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEYS 
or self
.GetMaintainAspectRatio()): 
2234                 newH 
= (newX2 
- newX1
) * (pt
._controlPointDragStartHeight 
/ pt
._controlPointDragStartWidth
) 
2235                 if pt
.GetY()>pt
._controlPointDragStartY
: 
2236                     newY2 
= newY1 
+ newH
 
2238                     newY1 
= newY2 
- newH
 
2240             newWidth 
= newX2 
- newX1
 
2241             newHeight 
= newY2 
- newY1
 
2243             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2244                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2246             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2247                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2249             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2 
2250             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2 
2251             if self
.GetFixedWidth(): 
2254             if self
.GetFixedHeight(): 
2257             pt
._controlPointDragEndWidth 
= newWidth
 
2258             pt
._controlPointDragEndHeight 
= newHeight
 
2259             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2261     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2262         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2263         self
.GetCanvas().PrepareDC(dc
) 
2265         if self
._canvas
.HasCapture(): 
2266             self
._canvas
.ReleaseMouse() 
2267         dc
.SetLogicalFunction(wx
.COPY
) 
2269         self
.ResetControlPoints() 
2273         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2275         # The next operation could destroy this control point (it does for 
2276         # label objects, via formatting the text), so save all values we're 
2277         # going to use, or we'll be accessing garbage. 
2281         if self
.GetCentreResize(): 
2282             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2284             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2286         # Recursively redraw links if we have a composite 
2287         if len(self
.GetChildren()): 
2288             self
.DrawLinks(dc
,-1, True) 
2290         width
, height 
= self
.GetBoundingBoxMax() 
2291         self
.GetEventHandler().OnEndSize(width
, height
) 
2293         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2294             self
._canvas
.Redraw(dc
) 
2298 class RectangleShape(Shape
): 
2300     The wxRectangleShape has rounded or square corners. 
2305     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2306         Shape
.__init
__(self
) 
2309         self
._cornerRadius 
= 0.0 
2310         self
.SetDefaultRegionSize() 
2312     def OnDraw(self
, dc
): 
2313         x1 
= self
._xpos 
- self
._width 
/ 2 
2314         y1 
= self
._ypos 
- self
._height 
/ 2 
2316         if self
._shadowMode 
!= SHADOW_NONE
: 
2317             if self
._shadowBrush
: 
2318                 dc
.SetBrush(self
._shadowBrush
) 
2319             dc
.SetPen(TransparentPen
) 
2321             if self
._cornerRadius
: 
2322                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2324                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2327             if self
._pen
.GetWidth() == 0: 
2328                 dc
.SetPen(TransparentPen
) 
2330                 dc
.SetPen(self
._pen
) 
2332             dc
.SetBrush(self
._brush
) 
2334         if self
._cornerRadius
: 
2335             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2337             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2339     def GetBoundingBoxMin(self
): 
2340         return self
._width
, self
._height
 
2342     def SetSize(self
, x
, y
, recursive 
= False): 
2343         self
.SetAttachmentSize(x
, y
) 
2344         self
._width 
= max(x
, 1) 
2345         self
._height 
= max(y
, 1) 
2346         self
.SetDefaultRegionSize() 
2348     def GetCornerRadius(self
): 
2349         """Get the radius of the rectangle's rounded corners.""" 
2350         return self
._cornerRadius
 
2352     def SetCornerRadius(self
, rad
): 
2353         """Set the radius of the rectangle's rounded corners. 
2355         If the radius is zero, a non-rounded rectangle will be drawn. 
2356         If the radius is negative, the value is the proportion of the smaller 
2357         dimension of the rectangle. 
2359         self
._cornerRadius 
= rad
 
2361     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2362     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2363         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2364         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2369     def GetHeight(self
): 
2372     def SetWidth(self
, w
): 
2375     def SetHeight(self
, h
): 
2380 class PolygonShape(Shape
): 
2381     """A PolygonShape's shape is defined by a number of points passed to 
2382     the object's constructor. It can be used to create new shapes such as 
2383     diamonds and triangles. 
2386         Shape
.__init
__(self
) 
2389         self
._originalPoints 
= None 
2391     def Create(self
, the_points 
= None): 
2392         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2398             self
._originalPoints 
= [] 
2401             self
._originalPoints 
= the_points
 
2403             # Duplicate the list of points 
2405             for point 
in the_points
: 
2406                 new_point 
= wx
.Point(point
[0], point
[1]) 
2407                 self
._points
.append(new_point
) 
2408             self
.CalculateBoundingBox() 
2409             self
._originalWidth 
= self
._boundWidth
 
2410             self
._originalHeight 
= self
._boundHeight
 
2411             self
.SetDefaultRegionSize() 
2413     def ClearPoints(self
): 
2415         self
._originalPoints 
= [] 
2417     # Width and height. Centre of object is centre of box 
2418     def GetBoundingBoxMin(self
): 
2419         return self
._boundWidth
, self
._boundHeight
 
2421     def GetPoints(self
): 
2422         """Return the internal list of polygon vertices.""" 
2425     def GetOriginalPoints(self
): 
2426         return self
._originalPoints
 
2428     def GetOriginalWidth(self
): 
2429         return self
._originalWidth
 
2431     def GetOriginalHeight(self
): 
2432         return self
._originalHeight
 
2434     def SetOriginalWidth(self
, w
): 
2435         self
._originalWidth 
= w
 
2437     def SetOriginalHeight(self
, h
): 
2438         self
._originalHeight 
= h
 
2440     def CalculateBoundingBox(self
): 
2441         # Calculate bounding box at construction (and presumably resize) time 
2447         for point 
in self
._points
: 
2458         self
._boundWidth 
= right 
- left
 
2459         self
._boundHeight 
= bottom 
- top
 
2461     def CalculatePolygonCentre(self
): 
2462         """Recalculates the centre of the polygon, and 
2463         readjusts the point offsets accordingly. 
2464         Necessary since the centre of the polygon 
2465         is expected to be the real centre of the bounding 
2473         for point 
in self
._points
: 
2484         bwidth 
= right 
- left
 
2485         bheight 
= bottom 
- top
 
2487         newCentreX 
= left 
+ bwidth 
/ 2 
2488         newCentreY 
= top 
+ bheight 
/ 2 
2490         for point 
in self
._points
: 
2491             point
.x 
-= newCentreX
 
2492             point
.y 
-= newCentreY
 
2493         self
._xpos 
+= newCentreX
 
2494         self
._ypos 
+= newCentreY
 
2496     def HitTest(self
, x
, y
): 
2497         # Imagine four lines radiating from this point. If all of these lines 
2498         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2499         # we'd need more radiating lines to be sure of correct results for 
2500         # very strange (concave) shapes. 
2501         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2502         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2507         for point 
in self
._points
: 
2508             xpoints
.append(point
.x 
+ self
._xpos
) 
2509             ypoints
.append(point
.y 
+ self
._ypos
) 
2511         # We assume it's inside the polygon UNLESS one or more 
2512         # lines don't hit the outline. 
2516             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2522         nearest_attachment 
= 0 
2524         # If a hit, check the attachment points within the object 
2527         for i 
in range(self
.GetNumberOfAttachments()): 
2528             e 
= self
.GetAttachmentPositionEdge(i
) 
2531                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2534                     nearest_attachment 
= i
 
2536         return nearest_attachment
, nearest
 
2538     # Really need to be able to reset the shape! Otherwise, if the 
2539     # points ever go to zero, we've lost it, and can't resize. 
2540     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2541         self
.SetAttachmentSize(new_width
, new_height
) 
2543         # Multiply all points by proportion of new size to old size 
2544         x_proportion 
= abs(new_width 
/ self
._originalWidth
) 
2545         y_proportion 
= abs(new_height 
/ self
._originalHeight
) 
2547         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2548             self
._points
[i
].x 
= self
._originalPoints
[i
][0] * x_proportion
 
2549             self
._points
[i
].y 
= self
._originalPoints
[i
][1] * y_proportion
 
2551         self
._boundWidth 
= abs(new_width
) 
2552         self
._boundHeight 
= abs(new_height
) 
2553         self
.SetDefaultRegionSize() 
2555     # Make the original points the same as the working points 
2556     def UpdateOriginalPoints(self
): 
2557         """If we've changed the shape, must make the original points match the 
2558         working points with this function. 
2560         self
._originalPoints 
= [] 
2562         for point 
in self
._points
: 
2563             original_point 
= wx
.RealPoint(point
.x
, point
.y
) 
2564             self
._originalPoints
.append(original_point
) 
2566         self
.CalculateBoundingBox() 
2567         self
._originalWidth 
= self
._boundWidth
 
2568         self
._originalHeight 
= self
._boundHeight
 
2570     def AddPolygonPoint(self
, pos
): 
2571         """Add a control point after the given point.""" 
2573             firstPoint 
= self
._points
[pos
] 
2575             firstPoint 
= self
._points
[0] 
2578             secondPoint 
= self
._points
[pos 
+ 1] 
2580             secondPoint 
= self
._points
[0] 
2582         x 
= (secondPoint
.x 
- firstPoint
.x
) / 2 + firstPoint
.x
 
2583         y 
= (secondPoint
.y 
- firstPoint
.y
) / 2 + firstPoint
.y
 
2584         point 
= wx
.RealPoint(x
, y
) 
2586         if pos 
>= len(self
._points
) - 1: 
2587             self
._points
.append(point
) 
2589             self
._points
.insert(pos 
+ 1, point
) 
2591         self
.UpdateOriginalPoints() 
2594             self
.DeleteControlPoints() 
2595             self
.MakeControlPoints() 
2597     def DeletePolygonPoint(self
, pos
): 
2598         """Delete the given control point.""" 
2599         if pos
<len(self
._points
): 
2600             del self
._points
[pos
] 
2601             self
.UpdateOriginalPoints() 
2603                 self
.DeleteControlPoints() 
2604                 self
.MakeControlPoints() 
2606     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2607     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2608         # First check for situation where the line is vertical, 
2609         # and we would want to connect to a point on that vertical -- 
2610         # oglFindEndForPolyline can't cope with this (the arrow 
2611         # gets drawn to the wrong place). 
2612         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2613             # Look for the point we'd be connecting to. This is 
2615             for point 
in self
._points
: 
2617                     if y2
>y1 
and point
.y
>0: 
2618                         return point
.x 
+ self
._xpos
, point
.y 
+ self
._ypos
 
2619                     elif y2
<y1 
and point
.y
<0: 
2620                         return point
.x 
+ self
._xpos
, point
.y 
+ self
._ypos
 
2624         for point 
in self
._points
: 
2625             xpoints
.append(point
.x 
+ self
._xpos
) 
2626             ypoints
.append(point
.y 
+ self
._ypos
) 
2628         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2630     def OnDraw(self
, dc
): 
2631         if self
._shadowMode 
!= SHADOW_NONE
: 
2632             if self
._shadowBrush
: 
2633                 dc
.SetBrush(self
._shadowBrush
) 
2634             dc
.SetPen(TransparentPen
) 
2636             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2639             if self
._pen
.GetWidth() == 0: 
2640                 dc
.SetPen(TransparentPen
) 
2642                 dc
.SetPen(self
._pen
) 
2644             dc
.SetBrush(self
._brush
) 
2645         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2647     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2648         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2649         # Multiply all points by proportion of new size to old size 
2650         x_proportion 
= abs(w 
/ self
._originalWidth
) 
2651         y_proportion 
= abs(h 
/ self
._originalHeight
) 
2654         for point 
in self
._originalPoints
: 
2655             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2656         dc
.DrawPolygon(intPoints
, x
, y
) 
2658     # Make as many control points as there are vertices 
2659     def MakeControlPoints(self
): 
2660         for point 
in self
._points
: 
2661             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
.x
, point
.y
) 
2662             self
._canvas
.AddShape(control
) 
2663             self
._controlPoints
.append(control
) 
2665     def ResetControlPoints(self
): 
2666         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2667             point 
= self
._points
[i
] 
2668             self
._controlPoints
[i
]._xoffset 
= point
.x
 
2669             self
._controlPoints
[i
]._yoffset 
= point
.y
 
2670             self
._controlPoints
[i
].polygonVertex 
= point
 
2672     def GetNumberOfAttachments(self
): 
2673         maxN 
= max(len(self
._points
) - 1, 0) 
2674         for point 
in self
._attachmentPoints
: 
2679     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2680         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment
<len(self
._points
): 
2681             point 
= self
._points
[0] 
2682             return point
.x 
+ self
._xpos
, point
.y 
+ self
._ypos
 
2683         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2685     def AttachmentIsValid(self
, attachment
): 
2686         if not self
._points
: 
2689         if attachment 
>= 0 and attachment
<len(self
._points
): 
2692         for point 
in self
._attachmentPoints
: 
2693             if point
._id 
== attachment
: 
2698     # Rotate about the given axis by the given amount in radians 
2699     def Rotate(self
, x
, y
, theta
): 
2700         actualTheta 
= theta 
- self
._rotation
 
2702         # Rotate attachment points 
2703         sinTheta 
= math
.sin(actualTheta
) 
2704         cosTheta 
= math
.cos(actualTheta
) 
2706         for point 
in self
._attachmentPoints
: 
2710             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2711             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2713         for point 
in self
._points
: 
2717             point
.x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2718             point
.y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2720         for point 
in self
._originalPoints
: 
2724             point
.x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2725             point
.y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2727         self
._rotation 
= theta
 
2729         self
.CalculatePolygonCentre() 
2730         self
.CalculateBoundingBox() 
2731         self
.ResetControlPoints() 
2733     # Control points ('handles') redirect control to the actual shape, to 
2734     # make it easier to override sizing behaviour. 
2735     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2736         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2737         self
.GetCanvas().PrepareDC(dc
) 
2739         dc
.SetLogicalFunction(OGLRBLF
) 
2741         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2742         dc
.SetPen(dottedPen
) 
2743         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2745         # Code for CTRL-drag in C++ version commented out 
2747         pt
.CalculateNewSize(x
, y
) 
2749         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize().x
, pt
.GetNewSize().y
) 
2751     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2752         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2753         self
.GetCanvas().PrepareDC(dc
) 
2757         dc
.SetLogicalFunction(OGLRBLF
) 
2759         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2761         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2763         pt
._originalDistance 
= dist
 
2764         pt
._originalSize
.x 
= bound_x
 
2765         pt
._originalSize
.y 
= bound_y
 
2767         if pt
._originalDistance 
== 0: 
2768             pt
._originalDistance 
= 0.0001 
2770         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2771         dc
.SetPen(dottedPen
) 
2772         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2774         # Code for CTRL-drag in C++ version commented out 
2776         pt
.CalculateNewSize(x
, y
) 
2778         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize().x
, pt
.GetNewSize().y
) 
2780         self
._canvas
.CaptureMouse() 
2782     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2783         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2784         self
.GetCanvas().PrepareDC(dc
) 
2786         if self
._canvas
.HasCapture(): 
2787             self
._canvas
.ReleaseMouse() 
2788         dc
.SetLogicalFunction(wx
.COPY
) 
2790         # If we're changing shape, must reset the original points 
2792             self
.CalculateBoundingBox() 
2793             self
.CalculatePolygonCentre() 
2795             self
.SetSize(pt
.GetNewSize().x
, pt
.GetNewSize().y
) 
2798         self
.ResetControlPoints() 
2799         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2800         if not self
._canvas
.GetQuickEditMode(): 
2801             self
._canvas
.Redraw(dc
) 
2805 class EllipseShape(Shape
): 
2806     """The EllipseShape behaves similarly to the RectangleShape but is 
2812     def __init__(self
, w
, h
): 
2813         Shape
.__init
__(self
) 
2816         self
.SetDefaultRegionSize() 
2818     def GetBoundingBoxMin(self
): 
2819         return self
._width
, self
._height
 
2821     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2822         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2824         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2829     def GetHeight(self
): 
2832     def SetWidth(self
, w
): 
2835     def SetHeight(self
, h
): 
2838     def OnDraw(self
, dc
): 
2839         if self
._shadowMode 
!= SHADOW_NONE
: 
2840             if self
._shadowBrush
: 
2841                 dc
.SetBrush(self
._shadowBrush
) 
2842             dc
.SetPen(TransparentPen
) 
2843             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2 + self
._shadowOffsetX
, 
2844                            self
._ypos 
- self
.GetHeight() / 2 + self
._shadowOffsetY
, 
2845                            self
.GetWidth(), self
.GetHeight()) 
2848             if self
._pen
.GetWidth() == 0: 
2849                 dc
.SetPen(TransparentPen
) 
2851                 dc
.SetPen(self
._pen
) 
2853             dc
.SetBrush(self
._brush
) 
2854         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2, self
._ypos 
- self
.GetHeight() / 2, self
.GetWidth(), self
.GetHeight()) 
2856     def SetSize(self
, x
, y
, recursive 
= True): 
2857         self
.SetAttachmentSize(x
, y
) 
2860         self
.SetDefaultRegionSize() 
2862     def GetNumberOfAttachments(self
): 
2863         return Shape
.GetNumberOfAttachments(self
) 
2865     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2866     # 2 = bottom, 3 = left. 
2867     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2868         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2869             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2871         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2872             top 
= self
._ypos 
+ self
._height 
/ 2 
2873             bottom 
= self
._ypos 
- self
._height 
/ 2 
2874             left 
= self
._xpos 
- self
._width 
/ 2 
2875             right 
= self
._xpos 
+ self
._width 
/ 2 
2877             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2879             if physicalAttachment 
== 0: 
2880                 if self
._spaceAttachments
: 
2881                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1) 
2885                 # We now have the point on the bounding box: but get the point 
2886                 # on the ellipse by imagining a vertical line from 
2887                 # (x, self._ypos - self_height - 500) to (x, self._ypos) intersecting 
2890                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2891             elif physicalAttachment 
== 1: 
2893                 if self
._spaceAttachments
: 
2894                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1) 
2897                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2898             elif physicalAttachment 
== 2: 
2899                 if self
._spaceAttachments
: 
2900                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1) 
2904                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2905             elif physicalAttachment 
== 3: 
2907                 if self
._spaceAttachments
: 
2908                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1) 
2911                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2913                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2915             return self
._xpos
, self
._ypos
 
2919 class CircleShape(EllipseShape
): 
2920     """An EllipseShape whose width and height are the same.""" 
2921     def __init__(self
, diameter
): 
2922         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2923         self
.SetMaintainAspectRatio(True) 
2925     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2926         return FindEndForCircle(self
._width 
/ 2, self
._xpos
, self
._ypos
, x2
, y2
) 
2930 class TextShape(RectangleShape
): 
2931     """As wxRectangleShape, but only the text is displayed.""" 
2932     def __init__(self
, width
, height
): 
2933         RectangleShape
.__init
__(self
, width
, height
) 
2935     def OnDraw(self
, dc
): 
2940 class ShapeRegion(object): 
2941     """Object region.""" 
2942     def __init__(self
, region 
= None): 
2944             self
._regionText 
= region
._regionText
 
2945             self
._regionName 
= region
._regionName
 
2946             self
._textColour 
= region
._textColour
 
2948             self
._font 
= region
._font
 
2949             self
._minHeight 
= region
._minHeight
 
2950             self
._minWidth 
= region
._minWidth
 
2951             self
._width 
= region
._width
 
2952             self
._height 
= region
._height
 
2956             self
._regionProportionX 
= region
._regionProportionX
 
2957             self
._regionProportionY 
= region
._regionProportionY
 
2958             self
._formatMode 
= region
._formatMode
 
2959             self
._actualColourObject 
= region
._actualColourObject
 
2960             self
._actualPenObject 
= None 
2961             self
._penStyle 
= region
._penStyle
 
2962             self
._penColour 
= region
._penColour
 
2965             for line 
in region
._formattedText
: 
2966                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2967                 self
._formattedText
.append(new_line
) 
2970             self
._font 
= NormalFont
 
2971             self
._minHeight 
= 5.0 
2972             self
._minWidth 
= 5.0 
2978             self
._regionProportionX
=-1.0 
2979             self
._regionProportionY
=-1.0 
2980             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2982             self
._textColour
="BLACK" 
2983             self
._penColour
="BLACK" 
2984             self
._penStyle 
= wx
.SOLID
 
2985             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2986             self
._actualPenObject 
= None 
2988         self
._formattedText 
= [] 
2990     def ClearText(self
): 
2991         self
._formattedText 
= [] 
2993     def SetFont(self
, f
): 
2996     def SetMinSize(self
, w
, h
): 
3000     def SetSize(self
, w
, h
): 
3004     def SetPosition(self
, xp
, yp
): 
3008     def SetProportions(self
, xp
, yp
): 
3009         self
._regionProportionX 
= xp
 
3010         self
._regionProportionY 
= yp
 
3012     def SetFormatMode(self
, mode
): 
3013         self
._formatMode 
= mode
 
3015     def SetColour(self
, col
): 
3016         self
._textColour 
= col
 
3017         self
._actualColourObject 
= col
 
3019     def GetActualColourObject(self
): 
3020         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3021         return self
._actualColourObject
 
3023     def SetPenColour(self
, col
): 
3024         self
._penColour 
= col
 
3025         self
._actualPenObject 
= None 
3027     # Returns NULL if the pen is invisible 
3028     # (different to pen being transparent; indicates that 
3029     # region boundary should not be drawn.) 
3030     def GetActualPen(self
): 
3031         if self
._actualPenObject
: 
3032             return self
._actualPenObject
 
3034         if not self
._penColour
: 
3036         if self
._penColour
=="Invisible": 
3038         self
._actualPenObject 
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
) 
3039         return self
._actualPenObject
 
3041     def SetText(self
, s
): 
3042         self
._regionText 
= s
 
3044     def SetName(self
, s
): 
3045         self
._regionName 
= s
 
3048         return self
._regionText
 
3053     def GetMinSize(self
): 
3054         return self
._minWidth
, self
._minHeight
 
3056     def GetProportion(self
): 
3057         return self
._regionProportionX
, self
._regionProportionY
 
3060         return self
._width
, self
._height
 
3062     def GetPosition(self
): 
3063         return self
._x
, self
._y
 
3065     def GetFormatMode(self
): 
3066         return self
._formatMode
 
3069         return self
._regionName
 
3071     def GetColour(self
): 
3072         return self
._textColour
 
3074     def GetFormattedText(self
): 
3075         return self
._formattedText
 
3077     def GetPenColour(self
): 
3078         return self
._penColour
 
3080     def GetPenStyle(self
): 
3081         return self
._penStyle
 
3083     def SetPenStyle(self
, style
): 
3084         self
._penStyle 
= style
 
3085         self
._actualPenObject 
= None 
3090     def GetHeight(self
): 
3095 class ControlPoint(RectangleShape
): 
3096     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3097         RectangleShape
.__init
__(self
, size
, size
) 
3099         self
._canvas 
= theCanvas
 
3100         self
._shape 
= object 
3101         self
._xoffset 
= the_xoffset
 
3102         self
._yoffset 
= the_yoffset
 
3103         self
._type 
= the_type
 
3104         self
.SetPen(BlackForegroundPen
) 
3105         self
.SetBrush(wx
.BLACK_BRUSH
) 
3106         self
._oldCursor 
= None 
3107         self
._visible 
= True 
3108         self
._eraseObject 
= True 
3110     # Don't even attempt to draw any text - waste of time 
3111     def OnDrawContents(self
, dc
): 
3114     def OnDraw(self
, dc
): 
3115         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3116         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3117         RectangleShape
.OnDraw(self
, dc
) 
3119     def OnErase(self
, dc
): 
3120         RectangleShape
.OnErase(self
, dc
) 
3122     # Implement resizing of canvas object 
3123     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3124         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3126     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3127         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3129     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3130         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3132     def GetNumberOfAttachments(self
): 
3135     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3136         return self
._xpos
, self
._ypos
 
3138     def SetEraseObject(self
, er
): 
3139         self
._eraseObject 
= er
 
3142 class PolygonControlPoint(ControlPoint
): 
3143     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3144         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3145         self
._polygonVertex 
= vertex
 
3146         self
._originalDistance 
= 0.0 
3147         self
._newSize 
= wx
.RealPoint() 
3148         self
._originalSize 
= wx
.RealPoint() 
3150     def GetNewSize(self
): 
3151         return self
._newSize
 
3153     # Calculate what new size would be, at end of resize 
3154     def CalculateNewSize(self
, x
, y
): 
3155         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3156         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3158         self
._newSize
.x 
= dist 
/ self
._originalDistance 
* self
._originalSize
.x
 
3159         self
._newSize
.y 
= dist 
/ self
._originalDistance 
* self
._originalSize
.y
 
3161     # Implement resizing polygon or moving the vertex 
3162     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3163         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3165     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3166         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3168     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3169         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3171 from _canvas 
import * 
3172 from _lines 
import * 
3173 from _composit 
import *