1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      The basic OGL shapes 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _oglmisc 
import * 
  23     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  24     global BlackForegroundPen
, NormalFont
 
  26     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  27     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  29     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  30     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  32     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  39 class ShapeTextLine(object): 
  40     def __init__(self
, the_x
, the_y
, the_line
): 
  57     def SetText(self
, text
): 
  65 class ShapeEvtHandler(object): 
  66     def __init__(self
, prev 
= None, shape 
= None): 
  67         self
._previousHandler 
= prev
 
  68         self
._handlerShape 
= shape
 
  70     def SetShape(self
, sh
): 
  71         self
._handlerShape 
= sh
 
  74         return self
._handlerShape
 
  76     def SetPreviousHandler(self
, handler
): 
  77         self
._previousHandler 
= handler
 
  79     def GetPreviousHandler(self
): 
  80         return self
._previousHandler
 
  83         if self
!=self
.GetShape(): 
  87         if self
._previousHandler
: 
  88             self
._previousHandler
.OnDraw(dc
) 
  90     def OnMoveLinks(self
, dc
): 
  91         if self
._previousHandler
: 
  92             self
._previousHandler
.OnMoveLinks(dc
) 
  94     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  95         if self
._previousHandler
: 
  96             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
  98     def OnDrawContents(self
, dc
): 
  99         if self
._previousHandler
: 
 100             self
._previousHandler
.OnDrawContents(dc
) 
 102     def OnDrawBranches(self
, dc
, erase 
= False): 
 103         if self
._previousHandler
: 
 104             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 106     def OnSize(self
, x
, y
): 
 107         if self
._previousHandler
: 
 108             self
._previousHandler
.OnSize(x
, y
) 
 110     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 111         if self
._previousHandler
: 
 112             return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
) 
 116     def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 117         if self
._previousHandler
: 
 118             return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
 122     def OnErase(self
, dc
): 
 123         if self
._previousHandler
: 
 124             self
._previousHandler
.OnErase(dc
) 
 126     def OnEraseContents(self
, dc
): 
 127         if self
._previousHandler
: 
 128             self
._previousHandler
.OnEraseContents(dc
) 
 130     def OnHighlight(self
, dc
): 
 131         if self
._previousHandler
: 
 132             self
._previousHandler
.OnHighlight(dc
) 
 134     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 135         if self
._previousHandler
: 
 136             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 138     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 139         if self
._previousHandler
: 
 140             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 142     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 143         if self
._previousHandler
: 
 144             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 146     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 147         if self
._previousHandler
: 
 148             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 150     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 151         if self
._previousHandler
: 
 152             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 154     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 155         if self
._previousHandler
: 
 156             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 158     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 159         if self
._previousHandler
: 
 160             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 162     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 163         if self
._previousHandler
: 
 164             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 166     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 167         if self
._previousHandler
: 
 168             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 170     # Control points ('handles') redirect control to the actual shape, 
 171     # to make it easier to override sizing behaviour. 
 172     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 173         if self
._previousHandler
: 
 174             self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
) 
 176     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 177         if self
._previousHandler
: 
 178             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 180     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 181         if self
._previousHandler
: 
 182             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 184     def OnBeginSize(self
, w
, h
): 
 187     def OnEndSize(self
, w
, h
): 
 190     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 191         if self
._previousHandler
: 
 192             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 194     def OnDrawControlPoints(self
, dc
): 
 195         if self
._previousHandler
: 
 196             self
._previousHandler
.OnDrawControlPoints(dc
) 
 198     def OnEraseControlPoints(self
, dc
): 
 199         if self
._previousHandler
: 
 200             self
._previousHandler
.OnEraseControlPoints(dc
) 
 202     # Can override this to prevent or intercept line reordering. 
 203     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 204         if self
._previousHandler
: 
 205             self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
) 
 209 class Shape(ShapeEvtHandler
): 
 214     The wxShape is the top-level, abstract object that all other objects 
 215     are derived from. All common functionality is represented by wxShape's 
 216     members, and overriden members that appear in derived classes and have 
 217     behaviour as documented for wxShape, are not documented separately. 
 220     GraphicsInSizeToContents 
= False 
 222     def __init__(self
, canvas 
= None): 
 223         ShapeEvtHandler
.__init
__(self
) 
 225         self
._eventHandler 
= self
 
 228         self
._formatted 
= False 
 229         self
._canvas 
= canvas
 
 232         self
._pen 
= BlackForegroundPen
 
 233         self
._brush 
= wx
.WHITE_BRUSH
 
 234         self
._font 
= NormalFont
 
 235         self
._textColour 
= wx
.BLACK
 
 236         self
._textColourName 
= wx
.BLACK
 
 237         self
._visible 
= False 
 238         self
._selected 
= False 
 239         self
._attachmentMode 
= ATTACHMENT_MODE_NONE
 
 240         self
._spaceAttachments 
= True 
 241         self
._disableLabel 
= False 
 242         self
._fixedWidth 
= False 
 243         self
._fixedHeight 
= False 
 244         self
._drawHandles 
= True 
 245         self
._sensitivity 
= OP_ALL
 
 246         self
._draggable 
= True 
 248         self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
 249         self
._shadowMode 
= SHADOW_NONE
 
 250         self
._shadowOffsetX 
= 6 
 251         self
._shadowOffsetY 
= 6 
 252         self
._shadowBrush 
= wx
.BLACK_BRUSH
 
 253         self
._textMarginX 
= 5 
 254         self
._textMarginY 
= 5 
 255         self
._regionName 
= "0" 
 256         self
._centreResize 
= True 
 257         self
._maintainAspectRatio 
= False 
 258         self
._highlighted 
= False 
 260         self
._branchNeckLength 
= 10 
 261         self
._branchStemLength 
= 10 
 262         self
._branchSpacing 
= 10 
 263         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 267         self
._controlPoints 
= [] 
 268         self
._attachmentPoints 
= [] 
 272         # Set up a default region. Much of the above will be put into 
 273         # the region eventually (the duplication is for compatibility) 
 274         region 
= ShapeRegion() 
 276         region
.SetFont(NormalFont
) 
 277         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 278         region
.SetColour("BLACK") 
 279         self
._regions
.append(region
) 
 282         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 284     def GetClassName(self
): 
 285         return str(self
.__class
__).split(".")[-1][:-2] 
 289         Fully disconnect this shape from parents, children, the 
 293             self
._parent
.GetChildren().remove(self
) 
 295         for child 
in self
.GetChildren(): 
 300         self
.ClearAttachments() 
 302         self
._handlerShape 
= None 
 305             self
.RemoveFromCanvas(self
._canvas
) 
 307         if self
.GetEventHandler(): 
 308             self
.GetEventHandler().OnDelete() 
 309         self
._eventHandler 
= None 
 312         """TRUE if the shape may be dragged by the user.""" 
 315     def SetShape(self
, sh
): 
 316         self
._handlerShape 
= sh
 
 319         """Get the internal canvas.""" 
 322     def GetBranchStyle(self
): 
 323         return self
._branchStyle
 
 325     def GetRotation(self
): 
 326         """Return the angle of rotation in radians.""" 
 327         return self
._rotation
 
 329     def SetRotation(self
, rotation
): 
 330         self
._rotation 
= rotation
 
 332     def SetHighlight(self
, hi
, recurse 
= False): 
 333         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 334         self
._highlighted 
= hi
 
 336             for shape 
in self
._children
: 
 337                 shape
.SetHighlight(hi
, recurse
) 
 339     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 340         """Set the shape to be sensitive or insensitive to specific mouse 
 343         sens is a bitlist of the following: 
 349         * OP_ALL (equivalent to a combination of all the above). 
 351         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 353         self
._sensitivity 
= sens
 
 355             for shape 
in self
._children
: 
 356                 shape
.SetSensitivityFilter(sens
, True) 
 358     def SetDraggable(self
, drag
, recursive 
= False): 
 359         """Set the shape to be draggable or not draggable.""" 
 360         self
._draggable 
= drag
 
 362             self
._sensitivity |
= OP_DRAG_LEFT
 
 363         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 364             self
._sensitivity 
-= OP_DRAG_LEFT
 
 367             for shape 
in self
._children
: 
 368                 shape
.SetDraggable(drag
, True) 
 370     def SetDrawHandles(self
, drawH
): 
 371         """Set the drawHandles flag for this shape and all descendants. 
 372         If drawH is TRUE (the default), any handles (control points) will 
 373         be drawn. Otherwise, the handles will not be drawn. 
 375         self
._drawHandles 
= drawH
 
 376         for shape 
in self
._children
: 
 377             shape
.SetDrawHandles(drawH
) 
 379     def SetShadowMode(self
, mode
, redraw 
= False): 
 380         """Set the shadow mode (whether a shadow is drawn or not). 
 381         mode can be one of the following: 
 384           No shadow (the default).  
 386           Shadow on the left side.  
 388           Shadow on the right side. 
 390         if redraw 
and self
.GetCanvas(): 
 391             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 392             self
.GetCanvas().PrepareDC(dc
) 
 394             self
._shadowMode 
= mode
 
 397             self
._shadowMode 
= mode
 
 399     def GetShadowMode(self
): 
 400         """Return the current shadow mode setting""" 
 401         return self
._shadowMode
 
 403     def SetCanvas(self
, theCanvas
): 
 404         """Identical to Shape.Attach.""" 
 405         self
._canvas 
= theCanvas
 
 406         for shape 
in self
._children
: 
 407             shape
.SetCanvas(theCanvas
) 
 409     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 410         """Add the shape to the canvas's shape list. 
 411         If addAfter is non-NULL, will add the shape after this one. 
 413         theCanvas
.AddShape(self
, addAfter
) 
 416         for object in self
._children
: 
 417             object.AddToCanvas(theCanvas
, lastImage
) 
 420     def InsertInCanvas(self
, theCanvas
): 
 421         """Insert the shape at the front of the shape list of canvas.""" 
 422         theCanvas
.InsertShape(self
) 
 425         for object in self
._children
: 
 426             object.AddToCanvas(theCanvas
, lastImage
) 
 429     def RemoveFromCanvas(self
, theCanvas
): 
 430         """Remove the shape from the canvas.""" 
 435         theCanvas
.RemoveShape(self
) 
 436         for object in self
._children
: 
 437             object.RemoveFromCanvas(theCanvas
) 
 439     def ClearAttachments(self
): 
 440         """Clear internal custom attachment point shapes (of class 
 443         self
._attachmentPoints 
= [] 
 445     def ClearText(self
, regionId 
= 0): 
 446         """Clear the text from the specified text region.""" 
 449         if regionId 
< len(self
._regions
): 
 450             self
._regions
[regionId
].ClearText() 
 452     def ClearRegions(self
): 
 453         """Clear the ShapeRegions from the shape.""" 
 456     def AddRegion(self
, region
): 
 457         """Add a region to the shape.""" 
 458         self
._regions
.append(region
) 
 460     def SetDefaultRegionSize(self
): 
 461         """Set the default region to be consistent with the shape size.""" 
 462         if not self
._regions
: 
 464         w
, h 
= self
.GetBoundingBoxMax() 
 465         self
._regions
[0].SetSize(w
, h
) 
 467     def HitTest(self
, x
, y
): 
 468         """Given a point on a canvas, returns TRUE if the point was on the 
 469         shape, and returns the nearest attachment point and distance from 
 470         the given point and target. 
 472         width
, height 
= self
.GetBoundingBoxMax() 
 478         width 
+= 4 # Allowance for inaccurate mousing 
 481         left 
= self
._xpos 
- width 
/ 2.0 
 482         top 
= self
._ypos 
- height 
/ 2.0 
 483         right 
= self
._xpos 
+ width 
/ 2.0 
 484         bottom 
= self
._ypos 
+ height 
/ 2.0 
 486         nearest_attachment 
= 0 
 488         # If within the bounding box, check the attachment points 
 490         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 491             n 
= self
.GetNumberOfAttachments() 
 494             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 495             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 498                 e 
= self
.GetAttachmentPositionEdge(i
) 
 501                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 504                         nearest_attachment 
= i
 
 506             return nearest_attachment
, nearest
 
 509     # Format a text string according to the region size, adding 
 510     # strings with positions to region text list 
 512     def FormatText(self
, dc
, s
, i 
= 0): 
 513         """Reformat the given text region; defaults to formatting the 
 518         if not self
._regions
: 
 521         if i 
> len(self
._regions
): 
 524         region 
= self
._regions
[i
] 
 525         region
._regionText 
= s
 
 526         dc
.SetFont(region
.GetFont()) 
 528         w
, h 
= region
.GetSize() 
 530         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 532             line 
= ShapeTextLine(0.0, 0.0, s
) 
 533             region
.GetFormattedText().append(line
) 
 537         # Don't try to resize an object with more than one image (this 
 538         # case should be dealt with by overriden handlers) 
 539         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 540            len(region
.GetFormattedText()) and \
 
 541            len(self
._regions
) == 1 and \
 
 542            not Shape
.GraphicsInSizeToContents
: 
 544             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 545             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 546                 # If we are a descendant of a composite, must make sure 
 547                 # the composite gets resized properly 
 549                 topAncestor 
= self
.GetTopAncestor() 
 550                 if topAncestor 
!= self
: 
 551                     Shape
.GraphicsInSizeToContents 
= True 
 553                     composite 
= topAncestor
 
 555                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 556                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 557                     composite
.CalculateSize() 
 558                     if composite
.Selected(): 
 559                         composite
.DeleteControlPoints(dc
) 
 560                         composite
.MakeControlPoints() 
 561                         composite
.MakeMandatoryControlPoints() 
 562                     # Where infinite recursion might happen if we didn't stop it 
 564                     Shape
.GraphicsInSizeToContents 
= False 
 568                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 569                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 570                 self
.EraseContents(dc
) 
 571         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 572         self
._formatted 
= True 
 574     def Recentre(self
, dc
): 
 575         """Do recentring (or other formatting) for all the text regions 
 578         w
, h 
= self
.GetBoundingBoxMin() 
 579         for region 
in self
._regions
: 
 580             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 582     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 583         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 584         the shape. Returns False if the line doesn't hit the perimeter. 
 588     def SetPen(self
, the_pen
): 
 589         """Set the pen for drawing the shape's outline.""" 
 592     def SetBrush(self
, the_brush
): 
 593         """Set the brush for filling the shape's shape.""" 
 594         self
._brush 
= the_brush
 
 596     # Get the top - most (non-division) ancestor, or self 
 597     def GetTopAncestor(self
): 
 598         """Return the top-most ancestor of this shape (the root of 
 601         if not self
.GetParent(): 
 604         if isinstance(self
.GetParent(), DivisionShape
): 
 606         return self
.GetParent().GetTopAncestor() 
 609     def SetFont(self
, the_font
, regionId 
= 0): 
 610         """Set the font for the specified text region.""" 
 611         self
._font 
= the_font
 
 612         if regionId 
< len(self
._regions
): 
 613             self
._regions
[regionId
].SetFont(the_font
) 
 615     def GetFont(self
, regionId 
= 0): 
 616         """Get the font for the specified text region.""" 
 617         if regionId 
>= len(self
._regions
): 
 619         return self
._regions
[regionId
].GetFont() 
 621     def SetFormatMode(self
, mode
, regionId 
= 0): 
 622         """Set the format mode of the default text region. The argument 
 623         can be a bit list of the following: 
 632         if regionId 
< len(self
._regions
): 
 633             self
._regions
[regionId
].SetFormatMode(mode
) 
 635     def GetFormatMode(self
, regionId 
= 0): 
 636         if regionId 
>= len(self
._regions
): 
 638         return self
._regions
[regionId
].GetFormatMode() 
 640     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 641         """Set the colour for the specified text region.""" 
 642         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 643         self
._textColourName 
= the_colour
 
 645         if regionId 
< len(self
._regions
): 
 646             self
._regions
[regionId
].SetColour(the_colour
) 
 648     def GetTextColour(self
, regionId 
= 0): 
 649         """Get the colour for the specified text region.""" 
 650         if regionId 
>= len(self
._regions
): 
 652         return self
._regions
[regionId
].GetColour() 
 654     def SetRegionName(self
, name
, regionId 
= 0): 
 655         """Set the name for this region. 
 656         The name for a region is unique within the scope of the whole 
 657         composite, whereas a region id is unique only for a single image. 
 659         if regionId 
< len(self
._regions
): 
 660             self
._regions
[regionId
].SetName(name
) 
 662     def GetRegionName(self
, regionId 
= 0): 
 663         """Get the region's name. 
 664         A region's name can be used to uniquely determine a region within 
 665         an entire composite image hierarchy. See also Shape.SetRegionName. 
 667         if regionId 
>= len(self
._regions
): 
 669         return self
._regions
[regionId
].GetName() 
 671     def GetRegionId(self
, name
): 
 672         """Get the region's identifier by name. 
 673         This is not unique for within an entire composite, but is unique 
 676         for i
, r 
in enumerate(self
._regions
): 
 677             if r
.GetName() == name
: 
 681     # Name all _regions in all subimages recursively 
 682     def NameRegions(self
, parentName
=""): 
 683         """Make unique names for all the regions in a shape or composite shape.""" 
 684         n 
= self
.GetNumberOfTextRegions() 
 687                 buff 
= parentName
+"."+str(i
) 
 690             self
.SetRegionName(buff
, i
) 
 692         for j
, child 
in enumerate(self
._children
): 
 694                 buff 
= parentName
+"."+str(j
) 
 697             child
.NameRegions(buff
) 
 699     # Get a region by name, possibly looking recursively into composites 
 700     def FindRegion(self
, name
): 
 701         """Find the actual image ('this' if non-composite) and region id 
 702         for the given region name. 
 704         id = self
.GetRegionId(name
) 
 708         for child 
in self
._children
: 
 709             actualImage
, regionId 
= child
.FindRegion(name
) 
 711                 return actualImage
, regionId
 
 715     # Finds all region names for this image (composite or simple). 
 716     def FindRegionNames(self
): 
 717         """Get a list of all region names for this image (composite or simple).""" 
 719         n 
= self
.GetNumberOfTextRegions() 
 721             list.append(self
.GetRegionName(i
)) 
 723         for child 
in self
._children
: 
 724             list += child
.FindRegionNames() 
 728     def AssignNewIds(self
): 
 729         """Assign new ids to this image and its children.""" 
 730         self
._id 
= wx
.NewId() 
 731         for child 
in self
._children
: 
 734     def OnDraw(self
, dc
): 
 737     def OnMoveLinks(self
, dc
): 
 738         # Want to set the ends of all attached links 
 739         # to point to / from this object 
 741         for line 
in self
._lines
: 
 742             line
.GetEventHandler().OnMoveLink(dc
) 
 744     def OnDrawContents(self
, dc
): 
 745         if not self
._regions
: 
 748         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 753         for region 
in self
._regions
: 
 755                 dc
.SetFont(region
.GetFont()) 
 757             dc
.SetTextForeground(region
.GetActualColourObject()) 
 758             dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 759             if not self
._formatted
: 
 760                 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 761                 self
._formatted 
= True 
 763             if not self
.GetDisableLabel(): 
 764                 DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 767     def DrawContents(self
, dc
): 
 768         """Draw the internal graphic of the shape (such as text). 
 770         Do not override this function: override OnDrawContents, which 
 771         is called by this function. 
 773         self
.GetEventHandler().OnDrawContents(dc
) 
 775     def OnSize(self
, x
, y
): 
 778     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 781     def OnErase(self
, dc
): 
 782         if not self
._visible
: 
 786         for line 
in self
._lines
: 
 787             line
.GetEventHandler().OnErase(dc
) 
 789         self
.GetEventHandler().OnEraseContents(dc
) 
 791     def OnEraseContents(self
, dc
): 
 792         if not self
._visible
: 
 795         xp
, yp 
= self
.GetX(), self
.GetY() 
 796         minX
, minY 
= self
.GetBoundingBoxMin() 
 797         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 799         topLeftX 
= xp 
- maxX 
/ 2.0 - 2 
 800         topLeftY 
= yp 
- maxY 
/ 2.0 - 2 
 804             penWidth 
= self
._pen
.GetWidth() 
 806         dc
.SetPen(self
.GetBackgroundPen()) 
 807         dc
.SetBrush(self
.GetBackgroundBrush()) 
 809         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 811     def EraseLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 812         """Erase links attached to this shape, but do not repair damage 
 813         caused to other shapes. 
 815         if not self
._visible
: 
 818         for line 
in self
._lines
: 
 819             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 820                 line
.GetEventHandler().OnErase(dc
) 
 823             for child 
in self
._children
: 
 824                 child
.EraseLinks(dc
, attachment
, recurse
) 
 826     def DrawLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 827         """Draws any lines linked to this shape.""" 
 828         if not self
._visible
: 
 831         for line 
in self
._lines
: 
 832             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 836             for child 
in self
._children
: 
 837                 child
.DrawLinks(dc
, attachment
, recurse
) 
 839     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 840     #  another on an edge of the shape. 
 841     # attachmentPoint is the attachment point (= side) in question. 
 843     # This is the default, rectangular implementation. 
 844     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 845         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 846         that one point comes before another on an edge of the shape. 
 848         attachment is the attachment point (side) in question. 
 850         This function is used in Shape.MoveLineToNewAttachment to determine 
 851         the new line ordering. 
 853         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 854         if physicalAttachment 
in [0, 2]: 
 855             return pt1
[0] <= pt2
[0] 
 856         elif physicalAttachment 
in [1, 3]: 
 857             return pt1
[1] <= pt2
[1] 
 861     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 862         """Move the given line (which must already be attached to the shape) 
 863         to a different attachment point on the shape, or a different order 
 864         on the same attachment. 
 866         Calls Shape.AttachmentSortTest and then 
 867         ShapeEvtHandler.OnChangeAttachment. 
 869         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 872         # Is (x, y) on this object? If so, find the new attachment point 
 873         # the user has moved the point to 
 874         hit 
= self
.HitTest(x
, y
) 
 878         newAttachment
, distance 
= hit
 
 882         if to_move
.GetTo() == self
: 
 883             oldAttachment 
= to_move
.GetAttachmentTo() 
 885             oldAttachment 
= to_move
.GetAttachmentFrom() 
 887         # The links in a new ordering 
 888         # First, add all links to the new list 
 889         newOrdering 
= self
._lines
[:] 
 891         # Delete the line object from the list of links; we're going to move 
 892         # it to another position in the list 
 893         del newOrdering
[newOrdering
.index(to_move
)] 
 900         for line 
in newOrdering
: 
 901             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 902                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 903                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 904                 if line
.GetTo() == self
: 
 911                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 912                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 913                 newPoint 
= wx
.RealPoint(x
, y
) 
 915                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 917                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 925             newOrdering
.append(to_move
) 
 927         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 930     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 931         if line
.GetTo() == self
: 
 932             line
.SetAttachmentTo(attachment
) 
 934             line
.SetAttachmentFrom(attachment
) 
 936         self
.ApplyAttachmentOrdering(ordering
) 
 938         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 939         self
.GetCanvas().PrepareDC(dc
) 
 942         if not self
.GetCanvas().GetQuickEditMode(): 
 943             self
.GetCanvas().Redraw(dc
) 
 945     # Reorders the lines according to the given list 
 946     def ApplyAttachmentOrdering(self
, linesToSort
): 
 947         """Apply the line ordering in linesToSort to the shape, to reorder 
 948         the way lines are attached. 
 950         linesStore 
= self
._lines
[:] 
 954         for line 
in linesToSort
: 
 955             if line 
in linesStore
: 
 956                 del linesStore
[linesStore
.index(line
)] 
 957                 self
._lines
.append(line
) 
 959         # Now add any lines that haven't been listed in linesToSort 
 960         self
._lines 
+= linesStore
 
 962     def SortLines(self
, attachment
, linesToSort
): 
 963         """ Reorder the lines coming into the node image at this attachment 
 964         position, in the order in which they appear in linesToSort. 
 966         Any remaining lines not in the list will be added to the end. 
 968         # This is a temporary store of all the lines at this attachment 
 969         # point. We'll tick them off as we've processed them. 
 970         linesAtThisAttachment 
= [] 
 972         for line 
in self
._lines
[:]: 
 973             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 974                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 975                 linesAtThisAttachment
.append(line
) 
 976                 del self
._lines
[self
._lines
.index(line
)] 
 978         for line 
in linesToSort
: 
 979             if line 
in linesAtThisAttachment
: 
 981                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 982                 self
._lines
.append(line
) 
 984         # Now add any lines that haven't been listed in linesToSort 
 985         self
._lines 
+= linesAtThisAttachment
 
 987     def OnHighlight(self
, dc
): 
 990     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 991         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 993                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 994                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 996     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 997         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
 998             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 999             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
1001     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1002         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1004                 hit 
= self
._parent
.HitTest(x
, y
) 
1006                     attachment
, dist 
= hit
 
1007                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
1010         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1011         self
.GetCanvas().PrepareDC(dc
) 
1012         dc
.SetLogicalFunction(OGLRBLF
) 
1014         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1015         dc
.SetPen(dottedPen
) 
1016         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1018         xx 
= x 
+ DragOffsetX
 
1019         yy 
= y 
+ DragOffsetY
 
1021         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1022         w
, h 
= self
.GetBoundingBoxMax() 
1023         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1025     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1026         global DragOffsetX
, DragOffsetY
 
1028         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1030                 hit 
= self
._parent
.HitTest(x
, y
) 
1032                     attachment
, dist 
= hit
 
1033                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1036         DragOffsetX 
= self
._xpos 
- x
 
1037         DragOffsetY 
= self
._ypos 
- y
 
1039         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1040         self
.GetCanvas().PrepareDC(dc
) 
1042         # New policy: don't erase shape until end of drag. 
1044         xx 
= x 
+ DragOffsetX
 
1045         yy 
= y 
+ DragOffsetY
 
1046         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1047         dc
.SetLogicalFunction(OGLRBLF
) 
1049         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1050         dc
.SetPen(dottedPen
) 
1051         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1053         w
, h 
= self
.GetBoundingBoxMax() 
1054         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1055         self
._canvas
.CaptureMouse() 
1057     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1058         if self
._canvas
.HasCapture(): 
1059             self
._canvas
.ReleaseMouse() 
1060         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1062                 hit 
= self
._parent
.HitTest(x
, y
) 
1064                     attachment
, dist 
= hit
 
1065                 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1068         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1069         self
.GetCanvas().PrepareDC(dc
) 
1071         dc
.SetLogicalFunction(wx
.COPY
) 
1072         xx 
= x 
+ DragOffsetX
 
1073         yy 
= y 
+ DragOffsetY
 
1074         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1076         # New policy: erase shape at end of drag. 
1079         self
.Move(dc
, xx
, yy
) 
1080         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1081             self
._canvas
.Redraw(dc
) 
1083     def OnDragRight(self
, draw
, 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().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1090     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1091         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1093                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1094                 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
) 
1097     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1098         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1100                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1101                 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
) 
1104     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1105         points 
= [[x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1106                 [x 
+ w 
/ 2.0, y 
- h 
/ 2.0], 
1107                 [x 
+ w 
/ 2.0, y 
+ h 
/ 2.0], 
1108                 [x 
- w 
/ 2.0, y 
+ h 
/ 2.0], 
1109                 [x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1112         dc
.DrawLines(points
) 
1114     def Attach(self
, can
): 
1115         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1119         """Disassociates the shape from its canvas.""" 
1122     def Move(self
, dc
, x
, y
, display 
= True): 
1123         """Move the shape to the given position. 
1124         Redraw if display is TRUE. 
1129         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1132         self
._xpos
, self
._ypos 
= x
, y
 
1134         self
.ResetControlPoints() 
1141         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1143     def MoveLinks(self
, dc
): 
1144         """Redraw all the lines attached to the shape.""" 
1145         self
.GetEventHandler().OnMoveLinks(dc
) 
1148         """Draw the whole shape and any lines attached to it. 
1150         Do not override this function: override OnDraw, which is called 
1154             self
.GetEventHandler().OnDraw(dc
) 
1155             self
.GetEventHandler().OnDrawContents(dc
) 
1156             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1157             self
.GetEventHandler().OnDrawBranches(dc
) 
1160         """Flash the shape.""" 
1161         if self
.GetCanvas(): 
1162             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1163             self
.GetCanvas().PrepareDC(dc
) 
1165             dc
.SetLogicalFunction(OGLRBLF
) 
1167             dc
.SetLogicalFunction(wx
.COPY
) 
1170     def Show(self
, show
): 
1171         """Set a flag indicating whether the shape should be drawn.""" 
1172         self
._visible 
= show
 
1173         for child 
in self
._children
: 
1176     def Erase(self
, dc
): 
1178         Does not repair damage caused to other shapes. 
1180         self
.GetEventHandler().OnErase(dc
) 
1181         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1182         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1184     def EraseContents(self
, dc
): 
1185         """Erase the shape contents, that is, the area within the shape's 
1186         minimum bounding box. 
1188         self
.GetEventHandler().OnEraseContents(dc
) 
1190     def AddText(self
, string
): 
1191         """Add a line of text to the shape's default text region.""" 
1192         if not self
._regions
: 
1195         region 
= self
._regions
[0] 
1197         new_line 
= ShapeTextLine(0, 0, string
) 
1198         text 
= region
.GetFormattedText() 
1199         text
.append(new_line
) 
1201         self
._formatted 
= False 
1203     def SetSize(self
, x
, y
, recursive 
= True): 
1204         """Set the shape's size.""" 
1205         self
.SetAttachmentSize(x
, y
) 
1206         self
.SetDefaultRegionSize() 
1208     def SetAttachmentSize(self
, w
, h
): 
1209         width
, height 
= self
.GetBoundingBoxMin() 
1213             scaleX 
= float(w
) / width
 
1217             scaleY 
= float(h
) / height
 
1219         for point 
in self
._attachmentPoints
: 
1220             point
._x 
= point
._x 
* scaleX
 
1221             point
._y 
= point
._y 
* scaleY
 
1223     # Add line FROM this object 
1224     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom 
= -1, positionTo 
= -1): 
1225         """Add a line between this shape and the given other shape, at the 
1226         specified attachment points. 
1228         The position in the list of lines at each end can also be specified, 
1229         so that the line will be drawn at a particular point on its attachment 
1232         if positionFrom 
== -1: 
1233             if not line 
in self
._lines
: 
1234                 self
._lines
.append(line
) 
1236             # Don't preserve old ordering if we have new ordering instructions 
1238                 self
._lines
.remove(line
) 
1241             if positionFrom 
< len(self
._lines
): 
1242                 self
._lines
.insert(positionFrom
, line
) 
1244                 self
._lines
.append(line
) 
1246         if positionTo 
== -1: 
1247             if not other 
in other
._lines
: 
1248                 other
._lines
.append(line
) 
1250             # Don't preserve old ordering if we have new ordering instructions 
1252                 other
._lines
.remove(line
) 
1255             if positionTo 
< len(other
._lines
): 
1256                 other
._lines
.insert(positionTo
, line
) 
1258                 other
._lines
.append(line
) 
1262         line
.SetAttachments(attachFrom
, attachTo
) 
1264         dc 
= wx
.ClientDC(self
._canvas
) 
1265         self
._canvas
.PrepareDC(dc
) 
1268     def RemoveLine(self
, line
): 
1269         """Remove the given line from the shape's list of attached lines.""" 
1270         if line
.GetFrom() == self
: 
1271             line
.GetTo()._lines
.remove(line
) 
1273             line
.GetFrom()._lines
.remove(line
) 
1275         self
._lines
.remove(line
) 
1277     # Default - make 6 control points 
1278     def MakeControlPoints(self
): 
1279         """Make a list of control points (draggable handles) appropriate to 
1282         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1283         minX
, minY 
= self
.GetBoundingBoxMin() 
1285         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1286         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1288         # Offsets from main object 
1289         top 
= -heightMin 
/ 2.0 
1290         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1291         left 
= -widthMin 
/ 2.0 
1292         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1294         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1295         self
._canvas
.AddShape(control
) 
1296         self
._controlPoints
.append(control
) 
1298         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1299         self
._canvas
.AddShape(control
) 
1300         self
._controlPoints
.append(control
) 
1302         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1303         self
._canvas
.AddShape(control
) 
1304         self
._controlPoints
.append(control
) 
1306         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1307         self
._canvas
.AddShape(control
) 
1308         self
._controlPoints
.append(control
) 
1310         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1311         self
._canvas
.AddShape(control
) 
1312         self
._controlPoints
.append(control
) 
1314         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1315         self
._canvas
.AddShape(control
) 
1316         self
._controlPoints
.append(control
) 
1318         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1319         self
._canvas
.AddShape(control
) 
1320         self
._controlPoints
.append(control
) 
1322         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1323         self
._canvas
.AddShape(control
) 
1324         self
._controlPoints
.append(control
) 
1326     def MakeMandatoryControlPoints(self
): 
1327         """Make the mandatory control points. 
1329         For example, the control point on a dividing line should appear even 
1330         if the divided rectangle shape's handles should not appear (because 
1331         it is the child of a composite, and children are not resizable). 
1333         for child 
in self
._children
: 
1334             child
.MakeMandatoryControlPoints() 
1336     def ResetMandatoryControlPoints(self
): 
1337         """Reset the mandatory control points.""" 
1338         for child 
in self
._children
: 
1339             child
.ResetMandatoryControlPoints() 
1341     def ResetControlPoints(self
): 
1342         """Reset the positions of the control points (for instance when the 
1343         shape's shape has changed). 
1345         self
.ResetMandatoryControlPoints() 
1347         if len(self
._controlPoints
) == 0: 
1350         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1351         minX
, minY 
= self
.GetBoundingBoxMin() 
1353         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1354         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1356         # Offsets from main object 
1357         top 
= -heightMin 
/ 2.0 
1358         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1359         left 
= -widthMin 
/ 2.0 
1360         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1362         self
._controlPoints
[0]._xoffset 
= left
 
1363         self
._controlPoints
[0]._yoffset 
= top
 
1365         self
._controlPoints
[1]._xoffset 
= 0 
1366         self
._controlPoints
[1]._yoffset 
= top
 
1368         self
._controlPoints
[2]._xoffset 
= right
 
1369         self
._controlPoints
[2]._yoffset 
= top
 
1371         self
._controlPoints
[3]._xoffset 
= right
 
1372         self
._controlPoints
[3]._yoffset 
= 0 
1374         self
._controlPoints
[4]._xoffset 
= right
 
1375         self
._controlPoints
[4]._yoffset 
= bottom
 
1377         self
._controlPoints
[5]._xoffset 
= 0 
1378         self
._controlPoints
[5]._yoffset 
= bottom
 
1380         self
._controlPoints
[6]._xoffset 
= left
 
1381         self
._controlPoints
[6]._yoffset 
= bottom
 
1383         self
._controlPoints
[7]._xoffset 
= left
 
1384         self
._controlPoints
[7]._yoffset 
= 0 
1386     def DeleteControlPoints(self
, dc 
= None): 
1387         """Delete the control points (or handles) for the shape. 
1389         Does not redraw the shape. 
1391         for control 
in self
._controlPoints
[:]: 
1393                 control
.GetEventHandler().OnErase(dc
) 
1395             self
._controlPoints
.remove(control
) 
1396         self
._controlPoints 
= [] 
1398         # Children of divisions are contained objects, 
1400         if not isinstance(self
, DivisionShape
): 
1401             for child 
in self
._children
: 
1402                 child
.DeleteControlPoints(dc
) 
1404     def OnDrawControlPoints(self
, dc
): 
1405         if not self
._drawHandles
: 
1408         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1409         dc
.SetPen(wx
.BLACK_PEN
) 
1411         for control 
in self
._controlPoints
: 
1414         # Children of divisions are contained objects, 
1416         # This test bypasses the type facility for speed 
1417         # (critical when drawing) 
1419         if not isinstance(self
, DivisionShape
): 
1420             for child 
in self
._children
: 
1421                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1423     def OnEraseControlPoints(self
, dc
): 
1424         for control 
in self
._controlPoints
: 
1427         if not isinstance(self
, DivisionShape
): 
1428             for child 
in self
._children
: 
1429                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1431     def Select(self
, select
, dc 
= None): 
1432         """Select or deselect the given shape, drawing or erasing control points 
1433         (handles) as necessary. 
1435         self
._selected 
= select
 
1437             self
.MakeControlPoints() 
1438             # Children of divisions are contained objects, 
1440             if not isinstance(self
, DivisionShape
): 
1441                 for child 
in self
._children
: 
1442                     child
.MakeMandatoryControlPoints() 
1444                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1446             self
.DeleteControlPoints(dc
) 
1447             if not isinstance(self
, DivisionShape
): 
1448                 for child 
in self
._children
: 
1449                     child
.DeleteControlPoints(dc
) 
1452         """TRUE if the shape is currently selected.""" 
1453         return self
._selected
 
1455     def AncestorSelected(self
): 
1456         """TRUE if the shape's ancestor is currently selected.""" 
1459         if not self
.GetParent(): 
1461         return self
.GetParent().AncestorSelected() 
1463     def GetNumberOfAttachments(self
): 
1464         """Get the number of attachment points for this shape.""" 
1465         # Should return the MAXIMUM attachment point id here, 
1466         # so higher-level functions can iterate through all attachments, 
1467         # even if they're not contiguous. 
1469         if len(self
._attachmentPoints
) == 0: 
1473             for point 
in self
._attachmentPoints
: 
1474                 if point
._id 
> maxN
: 
1478     def AttachmentIsValid(self
, attachment
): 
1479         """TRUE if attachment is a valid attachment point.""" 
1480         if len(self
._attachmentPoints
) == 0: 
1481             return attachment 
in range(4) 
1483         for point 
in self
._attachmentPoints
: 
1484             if point
._id 
== attachment
: 
1488     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1489         """Get the position at which the given attachment point should be drawn. 
1491         If attachment isn't found among the attachment points of the shape, 
1494         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1495             return self
._xpos
, self
._ypos
 
1496         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1497             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1499         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1500             if len(self
._attachmentPoints
): 
1501                 for point 
in self
._attachmentPoints
: 
1502                     if point
._id 
== attachment
: 
1503                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1506                 # Assume is rectangular 
1507                 w
, h 
= self
.GetBoundingBoxMax() 
1508                 top 
= self
._ypos 
+ h 
/ 2.0 
1509                 bottom 
= self
._ypos 
- h 
/ 2.0 
1510                 left 
= self
._xpos 
- w 
/ 2.0 
1511                 right 
= self
._xpos 
+ w 
/ 2.0 
1514                 line 
and line
.IsEnd(self
) 
1516                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1519                 if physicalAttachment 
== 0: 
1520                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1521                 elif physicalAttachment 
== 1: 
1522                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1523                 elif physicalAttachment 
== 2: 
1524                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1525                 elif physicalAttachment 
== 3: 
1526                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1532     def GetBoundingBoxMax(self
): 
1533         """Get the maximum bounding box for the shape, taking into account 
1534         external features such as shadows. 
1536         ww
, hh 
= self
.GetBoundingBoxMin() 
1537         if self
._shadowMode 
!= SHADOW_NONE
: 
1538             ww 
+= self
._shadowOffsetX
 
1539             hh 
+= self
._shadowOffsetY
 
1542     def GetBoundingBoxMin(self
): 
1543         """Get the minimum bounding box for the shape, that defines the area 
1544         available for drawing the contents (such as text). 
1550     def HasDescendant(self
, image
): 
1551         """TRUE if image is a descendant of this composite.""" 
1554         for child 
in self
._children
: 
1555             if child
.HasDescendant(image
): 
1559     # Assuming the attachment lies along a vertical or horizontal line, 
1560     # calculate the position on that point. 
1561     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1562         """Assuming the attachment lies along a vertical or horizontal line, 
1563         calculate the position on that point. 
1568             The first point of the line repesenting the edge of the shape. 
1571             The second point of the line representing the edge of the shape. 
1574             The position on the edge (for example there may be 6 lines at 
1575             this attachment point, and this may be the 2nd line. 
1578             The number of lines at this edge. 
1585         This function expects the line to be either vertical or horizontal, 
1586         and determines which. 
1588         isEnd 
= line 
and line
.IsEnd(self
) 
1590         # Are we horizontal or vertical? 
1591         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1601             if self
._spaceAttachments
: 
1602                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1603                     # Align line according to the next handle along 
1604                     point 
= line
.GetNextControlPoint(self
) 
1605                     if point
[0] < firstPoint
[0]: 
1607                     elif point
[0] > secondPoint
[0]: 
1612                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1.0) 
1614                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint 
1617             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1626             if self
._spaceAttachments
: 
1627                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1628                     # Align line according to the next handle along 
1629                     point 
= line
.GetNextControlPoint(self
) 
1630                     if point
[1] < firstPoint
[1]: 
1632                     elif point
[1] > secondPoint
[1]: 
1637                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1.0) 
1639                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint 
1644     # Return the zero-based position in m_lines of line 
1645     def GetLinePosition(self
, line
): 
1646         """Get the zero-based position of line in the list of lines 
1650             return self
._lines
.index(line
) 
1658     # shoulder1 ->---------<- shoulder2 
1660     #                      <- branching attachment point N-1 
1662     def GetBranchingAttachmentInfo(self
, attachment
): 
1663         """Get information about where branching connections go. 
1665         Returns FALSE if there are no lines at this attachment. 
1667         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1669         # Number of lines at this attachment 
1670         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1675         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1676         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1678         neck 
= wx
.RealPoint() 
1679         shoulder1 
= wx
.RealPoint() 
1680         shoulder2 
= wx
.RealPoint() 
1682         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1683         if physicalAttachment 
== 0: 
1684             neck
[0] = self
.GetX() 
1685             neck
[1] = root
[1] - self
._branchNeckLength
 
1687             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1688             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1690             shoulder1
[1] = neck
[1] 
1691             shoulder2
[1] = neck
[1] 
1692         elif physicalAttachment 
== 1: 
1693             neck
[0] = root
[0] + self
._branchNeckLength
 
1696             shoulder1
[0] = neck
[0] 
1697             shoulder2
[0] = neck
[0] 
1699             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1700             shoulder1
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1701         elif physicalAttachment 
== 2: 
1702             neck
[0] = self
.GetX() 
1703             neck
[1] = root
[1] + self
._branchNeckLength
 
1705             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1706             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1708             shoulder1
[1] = neck
[1] 
1709             shoulder2
[1] = neck
[1] 
1710         elif physicalAttachment 
== 3: 
1711             neck
[0] = root
[0] - self
._branchNeckLength
 
1714             shoulder1
[0] = neck
[0] 
1715             shoulder2
[0] = neck
[0] 
1717             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1718             shoulder2
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1720             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1721         return root
, neck
, shoulder1
, shoulder2
 
1723     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1724         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1726         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1728         stemPt 
= wx
.RealPoint() 
1730         if physicalAttachment 
== 0: 
1731             pt
[1] = neck
[1] - self
._branchStemLength
 
1732             pt
[0] = shoulder1
[0] + n 
* self
._branchSpacing
 
1736         elif physicalAttachment 
== 2: 
1737             pt
[1] = neck
[1] + self
._branchStemLength
 
1738             pt
[0] = shoulder1
[0] + n 
* self
._branchStemLength
 
1742         elif physicalAttachment 
== 1: 
1743             pt
[0] = neck
[0] + self
._branchStemLength
 
1744             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1748         elif physicalAttachment 
== 3: 
1749             pt
[0] = neck
[0] - self
._branchStemLength
 
1750             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1755             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1759     def GetAttachmentLineCount(self
, attachment
): 
1760         """Get the number of lines at this attachment position.""" 
1762         for lineShape 
in self
._lines
: 
1763             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1765             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1769     def GetBranchingAttachmentRoot(self
, attachment
): 
1770         """Get the root point at the given attachment.""" 
1771         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1773         root 
= wx
.RealPoint() 
1775         width
, height 
= self
.GetBoundingBoxMax() 
1777         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1778         if physicalAttachment 
== 0: 
1779             root
[0] = self
.GetX() 
1780             root
[1] = self
.GetY() - height 
/ 2.0 
1781         elif physicalAttachment 
== 1: 
1782             root
[0] = self
.GetX() + width 
/ 2.0 
1783             root
[1] = self
.GetY() 
1784         elif physicalAttachment 
== 2: 
1785             root
[0] = self
.GetX() 
1786             root
[1] = self
.GetY() + height 
/ 2.0 
1787         elif physicalAttachment 
== 3: 
1788             root
[0] = self
.GetX() - width 
/ 2.0 
1789             root
[1] = self
.GetY() 
1791             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1795     # Draw or erase the branches (not the actual arcs though) 
1796     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1797         count 
= self
.GetAttachmentLineCount(attachment
) 
1801         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1804             dc
.SetPen(wx
.WHITE_PEN
) 
1805             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1807             dc
.SetPen(wx
.BLACK_PEN
) 
1808             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1811         dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1]) 
1814             # Draw shoulder-to-shoulder line 
1815             dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1]) 
1816         # Draw all the little branches 
1817         for i 
in range(count
): 
1818             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1819             dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1]) 
1821             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count 
> 1: 
1823                 dc
.DrawEllipse(stemPt
[0] - blobSize 
/ 2.0, stemPt
[1] - blobSize 
/ 2.0, blobSize
, blobSize
) 
1825     def OnDrawBranches(self
, dc
, erase 
= False): 
1826         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1828         for i 
in range(self
.GetNumberOfAttachments()): 
1829             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1831     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1832         """ Only get the attachment position at the _edge_ of the shape, 
1833         ignoring branching mode. This is used e.g. to indicate the edge of 
1834         interest, not the point on the attachment branch. 
1836         oldMode 
= self
._attachmentMode
 
1838         # Calculate as if to edge, not branch 
1839         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1840             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1841         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1842         self
._attachmentMode 
= oldMode
 
1846     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1847         """ Rotate the standard attachment point from physical 
1848         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1850         if RoughlyEqual(self
.GetRotation(), 0): 
1851             i 
= physicalAttachment
 
1852         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1853             i 
= physicalAttachment 
- 1 
1854         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1855             i 
= physicalAttachment 
- 2 
1856         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1857             i 
= physicalAttachment 
- 3 
1859             # Can't handle -- assume the same 
1860             return physicalAttachment
 
1867     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1868         """Rotate the standard attachment point from logical 
1869         to physical (0 is always North). 
1871         if RoughlyEqual(self
.GetRotation(), 0): 
1872             i 
= logicalAttachment
 
1873         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1874             i 
= logicalAttachment 
+ 1 
1875         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1876             i 
= logicalAttachment 
+ 2 
1877         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1878             i 
= logicalAttachment 
+ 3 
1880             return logicalAttachment
 
1887     def Rotate(self
, x
, y
, theta
): 
1888         """Rotate about the given axis by the given amount in radians.""" 
1889         self
._rotation 
= theta
 
1890         if self
._rotation 
< 0: 
1891             self
._rotation 
+= 2 * math
.pi
 
1892         elif self
._rotation 
> 2 * math
.pi
: 
1893             self
._rotation 
-= 2 * math
.pi
 
1895     def GetBackgroundPen(self
): 
1896         """Return pen of the right colour for the background.""" 
1897         if self
.GetCanvas(): 
1898             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1899         return WhiteBackgroundPen
 
1901     def GetBackgroundBrush(self
): 
1902         """Return brush of the right colour for the background.""" 
1903         if self
.GetCanvas(): 
1904             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1905         return WhiteBackgroundBrush
 
1908         """Get the x position of the centre of the shape.""" 
1912         """Get the y position of the centre of the shape.""" 
1916         """Set the x position of the shape.""" 
1920         """Set the y position of the shape.""" 
1923     def GetParent(self
): 
1924         """Return the parent of this shape, if it is part of a composite.""" 
1927     def SetParent(self
, p
): 
1930     def GetChildren(self
): 
1931         """Return the list of children for this shape.""" 
1932         return self
._children
 
1934     def GetDrawHandles(self
): 
1935         """Return the list of drawhandles.""" 
1936         return self
._drawHandles
 
1938     def GetEventHandler(self
): 
1939         """Return the event handler for this shape.""" 
1940         return self
._eventHandler
 
1942     def SetEventHandler(self
, handler
): 
1943         """Set the event handler for this shape.""" 
1944         self
._eventHandler 
= handler
 
1946     def Recompute(self
): 
1947         """Recomputes any constraints associated with the shape. 
1949         Normally applicable to CompositeShapes only, but harmless for 
1950         other classes of Shape. 
1954     def IsHighlighted(self
): 
1955         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1956         return self
._highlighted
 
1958     def GetSensitivityFilter(self
): 
1959         """Return the sensitivity filter, a bitlist of values. 
1961         See Shape.SetSensitivityFilter. 
1963         return self
._sensitivity
 
1965     def SetFixedSize(self
, x
, y
): 
1966         """Set the shape to be fixed size.""" 
1967         self
._fixedWidth 
= x
 
1968         self
._fixedHeight 
= y
 
1970     def GetFixedSize(self
): 
1971         """Return flags indicating whether the shape is of fixed size in 
1974         return self
._fixedWidth
, self
._fixedHeight
 
1976     def GetFixedWidth(self
): 
1977         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1978         return self
._fixedWidth
 
1980     def GetFixedHeight(self
): 
1981         """TRUE if the shape cannot be resized in the vertical plane.""" 
1982         return self
._fixedHeight
 
1984     def SetSpaceAttachments(self
, sp
): 
1985         """Indicate whether lines should be spaced out evenly at the point 
1986         they touch the node (sp = True), or whether they should join at a single 
1989         self
._spaceAttachments 
= sp
 
1991     def GetSpaceAttachments(self
): 
1992         """Return whether lines should be spaced out evenly at the point they 
1993         touch the node (True), or whether they should join at a single point 
1996         return self
._spaceAttachments
 
1998     def SetCentreResize(self
, cr
): 
1999         """Specify whether the shape is to be resized from the centre (the 
2000         centre stands still) or from the corner or side being dragged (the 
2001         other corner or side stands still). 
2003         self
._centreResize 
= cr
 
2005     def GetCentreResize(self
): 
2006         """TRUE if the shape is to be resized from the centre (the centre stands 
2007         still), or FALSE if from the corner or side being dragged (the other 
2008         corner or side stands still) 
2010         return self
._centreResize
 
2012     def SetMaintainAspectRatio(self
, ar
): 
2013         """Set whether a shape that resizes should not change the aspect ratio 
2014         (width and height should be in the original proportion). 
2016         self
._maintainAspectRatio 
= ar
 
2018     def GetMaintainAspectRatio(self
): 
2019         """TRUE if shape keeps aspect ratio during resize.""" 
2020         return self
._maintainAspectRatio
 
2023         """Return the list of lines connected to this shape.""" 
2026     def SetDisableLabel(self
, flag
): 
2027         """Set flag to TRUE to stop the default region being shown.""" 
2028         self
._disableLabel 
= flag
 
2030     def GetDisableLabel(self
): 
2031         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2032         return self
._disableLabel
 
2034     def SetAttachmentMode(self
, mode
): 
2035         """Set the attachment mode. 
2037         If TRUE, attachment points will be significant when drawing lines to 
2038         and from this shape. 
2039         If FALSE, lines will be drawn as if to the centre of the shape. 
2041         self
._attachmentMode 
= mode
 
2043     def GetAttachmentMode(self
): 
2044         """Return the attachment mode. 
2046         See Shape.SetAttachmentMode. 
2048         return self
._attachmentMode
 
2051         """Set the integer identifier for this shape.""" 
2055         """Return the integer identifier for this shape.""" 
2059         """TRUE if the shape is in a visible state, FALSE otherwise. 
2061         Note that this has nothing to do with whether the window is hidden 
2062         or the shape has scrolled off the canvas; it refers to the internal 
2065         return self
._visible
 
2068         """Return the pen used for drawing the shape's outline.""" 
2072         """Return the brush used for filling the shape.""" 
2075     def GetNumberOfTextRegions(self
): 
2076         """Return the number of text regions for this shape.""" 
2077         return len(self
._regions
) 
2079     def GetRegions(self
): 
2080         """Return the list of ShapeRegions.""" 
2081         return self
._regions
 
2083     # Control points ('handles') redirect control to the actual shape, to 
2084     # make it easier to override sizing behaviour. 
2085     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2086         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2088         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2089         self
.GetCanvas().PrepareDC(dc
) 
2091         dc
.SetLogicalFunction(OGLRBLF
) 
2093         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2094         dc
.SetPen(dottedPen
) 
2095         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2097         if self
.GetCentreResize(): 
2098             # Maintain the same centre point 
2099             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2100             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2102             # Constrain sizing according to what control point you're dragging 
2103             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2104                 if self
.GetMaintainAspectRatio(): 
2105                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2107                     new_height 
= bound_y
 
2108             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2109                 if self
.GetMaintainAspectRatio(): 
2110                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2113             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2114                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2116             if self
.GetFixedWidth(): 
2119             if self
.GetFixedHeight(): 
2120                 new_height 
= bound_y
 
2122             pt
._controlPointDragEndWidth 
= new_width
 
2123             pt
._controlPointDragEndHeight 
= new_height
 
2125             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2127             # Don't maintain the same centre point 
2128             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2129             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2130             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2131             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2132             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2133                 newY1 
= pt
._controlPointDragStartY
 
2134                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2135             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2136                 newX1 
= pt
._controlPointDragStartX
 
2137                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2138             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2139                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2140                 if self
.GetY() > pt
._controlPointDragStartY
: 
2141                     newY2 
= newY1 
+ newH
 
2143                     newY1 
= newY2 
- newH
 
2145             newWidth 
= float(newX2 
- newX1
) 
2146             newHeight 
= float(newY2 
- newY1
) 
2148             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2149                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2151             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2152                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2154             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2155             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2156             if self
.GetFixedWidth(): 
2159             if self
.GetFixedHeight(): 
2162             pt
._controlPointDragEndWidth 
= newWidth
 
2163             pt
._controlPointDragEndHeight 
= newHeight
 
2164             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2166     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2167         self
._canvas
.CaptureMouse() 
2169         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2170         self
.GetCanvas().PrepareDC(dc
) 
2172         dc
.SetLogicalFunction(OGLRBLF
) 
2174         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2175         self
.GetEventHandler().OnBeginSize(bound_x
, bound_y
) 
2177         # Choose the 'opposite corner' of the object as the stationary 
2178         # point in case this is non-centring resizing. 
2179         if pt
.GetX() < self
.GetX(): 
2180             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2.0 
2182             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2184         if pt
.GetY() < self
.GetY(): 
2185             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2.0 
2187             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2189         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2190             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2191         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2192             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2194         # We may require the old width and height 
2195         pt
._controlPointDragStartWidth 
= bound_x
 
2196         pt
._controlPointDragStartHeight 
= bound_y
 
2198         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2199         dc
.SetPen(dottedPen
) 
2200         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2202         if self
.GetCentreResize(): 
2203             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2204             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2206             # Constrain sizing according to what control point you're dragging 
2207             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2208                 if self
.GetMaintainAspectRatio(): 
2209                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2211                     new_height 
= bound_y
 
2212             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2213                 if self
.GetMaintainAspectRatio(): 
2214                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2217             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2218                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2220             if self
.GetFixedWidth(): 
2223             if self
.GetFixedHeight(): 
2224                 new_height 
= bound_y
 
2226             pt
._controlPointDragEndWidth 
= new_width
 
2227             pt
._controlPointDragEndHeight 
= new_height
 
2228             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2230             # Don't maintain the same centre point 
2231             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2232             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2233             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2234             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2235             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2236                 newY1 
= pt
._controlPointDragStartY
 
2237                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2238             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2239                 newX1 
= pt
._controlPointDragStartX
 
2240                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2241             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2242                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2243                 if pt
.GetY() > pt
._controlPointDragStartY
: 
2244                     newY2 
= newY1 
+ newH
 
2246                     newY1 
= newY2 
- newH
 
2248             newWidth 
= float(newX2 
- newX1
) 
2249             newHeight 
= float(newY2 
- newY1
) 
2251             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2252                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2254             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2255                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2257             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2258             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2259             if self
.GetFixedWidth(): 
2262             if self
.GetFixedHeight(): 
2265             pt
._controlPointDragEndWidth 
= newWidth
 
2266             pt
._controlPointDragEndHeight 
= newHeight
 
2267             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2269     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2270         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2271         self
.GetCanvas().PrepareDC(dc
) 
2273         if self
._canvas
.HasCapture(): 
2274             self
._canvas
.ReleaseMouse() 
2275         dc
.SetLogicalFunction(wx
.COPY
) 
2277         self
.ResetControlPoints() 
2281         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2283         # The next operation could destroy this control point (it does for 
2284         # label objects, via formatting the text), so save all values we're 
2285         # going to use, or we'll be accessing garbage. 
2289         if self
.GetCentreResize(): 
2290             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2292             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2294         # Recursively redraw links if we have a composite 
2295         if len(self
.GetChildren()): 
2296             self
.DrawLinks(dc
, -1, True) 
2298         width
, height 
= self
.GetBoundingBoxMax() 
2299         self
.GetEventHandler().OnEndSize(width
, height
) 
2301         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2302             self
._canvas
.Redraw(dc
) 
2306 class RectangleShape(Shape
): 
2308     The wxRectangleShape has rounded or square corners. 
2313     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2314         Shape
.__init
__(self
) 
2317         self
._cornerRadius 
= 0.0 
2318         self
.SetDefaultRegionSize() 
2320     def OnDraw(self
, dc
): 
2321         x1 
= self
._xpos 
- self
._width 
/ 2.0 
2322         y1 
= self
._ypos 
- self
._height 
/ 2.0 
2324         if self
._shadowMode 
!= SHADOW_NONE
: 
2325             if self
._shadowBrush
: 
2326                 dc
.SetBrush(self
._shadowBrush
) 
2327             dc
.SetPen(TransparentPen
) 
2329             if self
._cornerRadius
: 
2330                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2332                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2335             if self
._pen
.GetWidth() == 0: 
2336                 dc
.SetPen(TransparentPen
) 
2338                 dc
.SetPen(self
._pen
) 
2340             dc
.SetBrush(self
._brush
) 
2342         if self
._cornerRadius
: 
2343             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2345             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2347     def GetBoundingBoxMin(self
): 
2348         return self
._width
, self
._height
 
2350     def SetSize(self
, x
, y
, recursive 
= False): 
2351         self
.SetAttachmentSize(x
, y
) 
2352         self
._width 
= max(x
, 1) 
2353         self
._height 
= max(y
, 1) 
2354         self
.SetDefaultRegionSize() 
2356     def GetCornerRadius(self
): 
2357         """Get the radius of the rectangle's rounded corners.""" 
2358         return self
._cornerRadius
 
2360     def SetCornerRadius(self
, rad
): 
2361         """Set the radius of the rectangle's rounded corners. 
2363         If the radius is zero, a non-rounded rectangle will be drawn. 
2364         If the radius is negative, the value is the proportion of the smaller 
2365         dimension of the rectangle. 
2367         self
._cornerRadius 
= rad
 
2369     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2370     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2371         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2372         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2377     def GetHeight(self
): 
2380     def SetWidth(self
, w
): 
2383     def SetHeight(self
, h
): 
2388 class PolygonShape(Shape
): 
2389     """A PolygonShape's shape is defined by a number of points passed to 
2390     the object's constructor. It can be used to create new shapes such as 
2391     diamonds and triangles. 
2394         Shape
.__init
__(self
) 
2397         self
._originalPoints 
= None 
2399     def Create(self
, the_points 
= None): 
2400         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2406             self
._originalPoints 
= [] 
2409             self
._originalPoints 
= the_points
 
2411             # Duplicate the list of points 
2413             for point 
in the_points
: 
2414                 new_point 
= wx
.Point(point
[0], point
[1]) 
2415                 self
._points
.append(new_point
) 
2416             self
.CalculateBoundingBox() 
2417             self
._originalWidth 
= self
._boundWidth
 
2418             self
._originalHeight 
= self
._boundHeight
 
2419             self
.SetDefaultRegionSize() 
2421     def ClearPoints(self
): 
2423         self
._originalPoints 
= [] 
2425     # Width and height. Centre of object is centre of box 
2426     def GetBoundingBoxMin(self
): 
2427         return self
._boundWidth
, self
._boundHeight
 
2429     def GetPoints(self
): 
2430         """Return the internal list of polygon vertices.""" 
2433     def GetOriginalPoints(self
): 
2434         return self
._originalPoints
 
2436     def GetOriginalWidth(self
): 
2437         return self
._originalWidth
 
2439     def GetOriginalHeight(self
): 
2440         return self
._originalHeight
 
2442     def SetOriginalWidth(self
, w
): 
2443         self
._originalWidth 
= w
 
2445     def SetOriginalHeight(self
, h
): 
2446         self
._originalHeight 
= h
 
2448     def CalculateBoundingBox(self
): 
2449         # Calculate bounding box at construction (and presumably resize) time 
2455         for point 
in self
._points
: 
2458             if point
[0] > right
: 
2463             if point
[1] > bottom
: 
2466         self
._boundWidth 
= right 
- left
 
2467         self
._boundHeight 
= bottom 
- top
 
2469     def CalculatePolygonCentre(self
): 
2470         """Recalculates the centre of the polygon, and 
2471         readjusts the point offsets accordingly. 
2472         Necessary since the centre of the polygon 
2473         is expected to be the real centre of the bounding 
2481         for point 
in self
._points
: 
2484             if point
[0] > right
: 
2489             if point
[1] > bottom
: 
2492         bwidth 
= right 
- left
 
2493         bheight 
= bottom 
- top
 
2495         newCentreX 
= left 
+ bwidth 
/ 2.0 
2496         newCentreY 
= top 
+ bheight 
/ 2.0 
2498         for i 
in range(len(self
._points
)): 
2499             self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
 
2500         self
._xpos 
+= newCentreX
 
2501         self
._ypos 
+= newCentreY
 
2503     def HitTest(self
, x
, y
): 
2504         # Imagine four lines radiating from this point. If all of these lines 
2505         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2506         # we'd need more radiating lines to be sure of correct results for 
2507         # very strange (concave) shapes. 
2508         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2509         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2514         for point 
in self
._points
: 
2515             xpoints
.append(point
[0] + self
._xpos
) 
2516             ypoints
.append(point
[1] + self
._ypos
) 
2518         # We assume it's inside the polygon UNLESS one or more 
2519         # lines don't hit the outline. 
2523             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2529         nearest_attachment 
= 0 
2531         # If a hit, check the attachment points within the object 
2534         for i 
in range(self
.GetNumberOfAttachments()): 
2535             e 
= self
.GetAttachmentPositionEdge(i
) 
2538                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2541                     nearest_attachment 
= i
 
2543         return nearest_attachment
, nearest
 
2545     # Really need to be able to reset the shape! Otherwise, if the 
2546     # points ever go to zero, we've lost it, and can't resize. 
2547     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2548         self
.SetAttachmentSize(new_width
, new_height
) 
2550         # Multiply all points by proportion of new size to old size 
2551         x_proportion 
= abs(float(new_width
) / self
._originalWidth
) 
2552         y_proportion 
= abs(float(new_height
) / self
._originalHeight
) 
2554         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2555             self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
) 
2557         self
._boundWidth 
= abs(new_width
) 
2558         self
._boundHeight 
= abs(new_height
) 
2559         self
.SetDefaultRegionSize() 
2561     # Make the original points the same as the working points 
2562     def UpdateOriginalPoints(self
): 
2563         """If we've changed the shape, must make the original points match the 
2564         working points with this function. 
2566         self
._originalPoints 
= [] 
2568         for point 
in self
._points
: 
2569             original_point 
= wx
.RealPoint(point
[0], point
[1]) 
2570             self
._originalPoints
.append(original_point
) 
2572         self
.CalculateBoundingBox() 
2573         self
._originalWidth 
= self
._boundWidth
 
2574         self
._originalHeight 
= self
._boundHeight
 
2576     def AddPolygonPoint(self
, pos
): 
2577         """Add a control point after the given point.""" 
2579             firstPoint 
= self
._points
[pos
] 
2581             firstPoint 
= self
._points
[0] 
2584             secondPoint 
= self
._points
[pos 
+ 1] 
2586             secondPoint 
= self
._points
[0] 
2588         x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0] 
2589         y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1] 
2590         point 
= wx
.RealPoint(x
, y
) 
2592         if pos 
>= len(self
._points
) - 1: 
2593             self
._points
.append(point
) 
2595             self
._points
.insert(pos 
+ 1, point
) 
2597         self
.UpdateOriginalPoints() 
2600             self
.DeleteControlPoints() 
2601             self
.MakeControlPoints() 
2603     def DeletePolygonPoint(self
, pos
): 
2604         """Delete the given control point.""" 
2605         if pos 
< len(self
._points
): 
2606             del self
._points
[pos
] 
2607             self
.UpdateOriginalPoints() 
2609                 self
.DeleteControlPoints() 
2610                 self
.MakeControlPoints() 
2612     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2613     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2614         # First check for situation where the line is vertical, 
2615         # and we would want to connect to a point on that vertical -- 
2616         # oglFindEndForPolyline can't cope with this (the arrow 
2617         # gets drawn to the wrong place). 
2618         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2619             # Look for the point we'd be connecting to. This is 
2621             for point 
in self
._points
: 
2623                     if y2 
> y1 
and point
[1] > 0: 
2624                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2625                     elif y2 
< y1 
and point
[1] < 0: 
2626                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2630         for point 
in self
._points
: 
2631             xpoints
.append(point
[0] + self
._xpos
) 
2632             ypoints
.append(point
[1] + self
._ypos
) 
2634         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2636     def OnDraw(self
, dc
): 
2637         if self
._shadowMode 
!= SHADOW_NONE
: 
2638             if self
._shadowBrush
: 
2639                 dc
.SetBrush(self
._shadowBrush
) 
2640             dc
.SetPen(TransparentPen
) 
2642             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2645             if self
._pen
.GetWidth() == 0: 
2646                 dc
.SetPen(TransparentPen
) 
2648                 dc
.SetPen(self
._pen
) 
2650             dc
.SetBrush(self
._brush
) 
2651         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2653     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2654         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2655         # Multiply all points by proportion of new size to old size 
2656         x_proportion 
= abs(float(w
) / self
._originalWidth
) 
2657         y_proportion 
= abs(float(h
) / self
._originalHeight
) 
2660         for point 
in self
._originalPoints
: 
2661             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2662         dc
.DrawPolygon(intPoints
, x
, y
) 
2664     # Make as many control points as there are vertices 
2665     def MakeControlPoints(self
): 
2666         for point 
in self
._points
: 
2667             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1]) 
2668             self
._canvas
.AddShape(control
) 
2669             self
._controlPoints
.append(control
) 
2671     def ResetControlPoints(self
): 
2672         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2673             point 
= self
._points
[i
] 
2674             self
._controlPoints
[i
]._xoffset 
= point
[0] 
2675             self
._controlPoints
[i
]._yoffset 
= point
[1] 
2676             self
._controlPoints
[i
].polygonVertex 
= point
 
2678     def GetNumberOfAttachments(self
): 
2679         maxN 
= max(len(self
._points
) - 1, 0) 
2680         for point 
in self
._attachmentPoints
: 
2681             if point
._id 
> maxN
: 
2685     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2686         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment 
< len(self
._points
): 
2687             point 
= self
._points
[0] 
2688             return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2689         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2691     def AttachmentIsValid(self
, attachment
): 
2692         if not self
._points
: 
2695         if attachment 
>= 0 and attachment 
< len(self
._points
): 
2698         for point 
in self
._attachmentPoints
: 
2699             if point
._id 
== attachment
: 
2704     # Rotate about the given axis by the given amount in radians 
2705     def Rotate(self
, x
, y
, theta
): 
2706         actualTheta 
= theta 
- self
._rotation
 
2708         # Rotate attachment points 
2709         sinTheta 
= math
.sin(actualTheta
) 
2710         cosTheta 
= math
.cos(actualTheta
) 
2712         for point 
in self
._attachmentPoints
: 
2716             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2717             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2719         for i 
in range(len(self
._points
)): 
2720             x1
, y1 
= self
._points
[i
] 
2722             self
._points
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2724         for i 
in range(len(self
._originalPoints
)): 
2725             x1
, y1 
= self
._originalPoints
[i
] 
2727             self
._originalPoints
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2729         # Added by Pierre Hjälm. If we don't do this the outline will be 
2730         # the wrong size. Hopefully it won't have any ill effects. 
2731         self
.UpdateOriginalPoints() 
2733         self
._rotation 
= theta
 
2735         self
.CalculatePolygonCentre() 
2736         self
.CalculateBoundingBox() 
2737         self
.ResetControlPoints() 
2739     # Control points ('handles') redirect control to the actual shape, to 
2740     # make it easier to override sizing behaviour. 
2741     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2742         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2743         self
.GetCanvas().PrepareDC(dc
) 
2745         dc
.SetLogicalFunction(OGLRBLF
) 
2747         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2748         dc
.SetPen(dottedPen
) 
2749         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2751         # Code for CTRL-drag in C++ version commented out 
2753         pt
.CalculateNewSize(x
, y
) 
2755         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2757     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2758         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2759         self
.GetCanvas().PrepareDC(dc
) 
2763         dc
.SetLogicalFunction(OGLRBLF
) 
2765         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2767         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2769         pt
._originalDistance 
= dist
 
2770         pt
._originalSize
[0] = bound_x
 
2771         pt
._originalSize
[1] = bound_y
 
2773         if pt
._originalDistance 
== 0: 
2774             pt
._originalDistance 
= 0.0001 
2776         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2777         dc
.SetPen(dottedPen
) 
2778         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2780         # Code for CTRL-drag in C++ version commented out 
2782         pt
.CalculateNewSize(x
, y
) 
2784         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2786         self
._canvas
.CaptureMouse() 
2788     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2789         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2790         self
.GetCanvas().PrepareDC(dc
) 
2792         if self
._canvas
.HasCapture(): 
2793             self
._canvas
.ReleaseMouse() 
2794         dc
.SetLogicalFunction(wx
.COPY
) 
2796         # If we're changing shape, must reset the original points 
2798             self
.CalculateBoundingBox() 
2799             self
.CalculatePolygonCentre() 
2801             self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2804         self
.ResetControlPoints() 
2805         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2806         if not self
._canvas
.GetQuickEditMode(): 
2807             self
._canvas
.Redraw(dc
) 
2811 class EllipseShape(Shape
): 
2812     """The EllipseShape behaves similarly to the RectangleShape but is 
2818     def __init__(self
, w
, h
): 
2819         Shape
.__init
__(self
) 
2822         self
.SetDefaultRegionSize() 
2824     def GetBoundingBoxMin(self
): 
2825         return self
._width
, self
._height
 
2827     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2828         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2830         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2835     def GetHeight(self
): 
2838     def SetWidth(self
, w
): 
2841     def SetHeight(self
, h
): 
2844     def OnDraw(self
, dc
): 
2845         if self
._shadowMode 
!= SHADOW_NONE
: 
2846             if self
._shadowBrush
: 
2847                 dc
.SetBrush(self
._shadowBrush
) 
2848             dc
.SetPen(TransparentPen
) 
2849             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
, 
2850                            self
._ypos 
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
, 
2851                            self
.GetWidth(), self
.GetHeight()) 
2854             if self
._pen
.GetWidth() == 0: 
2855                 dc
.SetPen(TransparentPen
) 
2857                 dc
.SetPen(self
._pen
) 
2859             dc
.SetBrush(self
._brush
) 
2860         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0, self
._ypos 
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight()) 
2862     def SetSize(self
, x
, y
, recursive 
= True): 
2863         self
.SetAttachmentSize(x
, y
) 
2866         self
.SetDefaultRegionSize() 
2868     def GetNumberOfAttachments(self
): 
2869         return Shape
.GetNumberOfAttachments(self
) 
2871     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2872     # 2 = bottom, 3 = left. 
2873     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2874         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2875             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2877         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2878             top 
= self
._ypos 
+ self
._height 
/ 2.0 
2879             bottom 
= self
._ypos 
- self
._height 
/ 2.0 
2880             left 
= self
._xpos 
- self
._width 
/ 2.0 
2881             right 
= self
._xpos 
+ self
._width 
/ 2.0 
2883             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2885             if physicalAttachment 
== 0: 
2886                 if self
._spaceAttachments
: 
2887                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2891                 # We now have the point on the bounding box: but get the point 
2892                 # on the ellipse by imagining a vertical line from 
2893                 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 
2896                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2897             elif physicalAttachment 
== 1: 
2899                 if self
._spaceAttachments
: 
2900                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2903                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2904             elif physicalAttachment 
== 2: 
2905                 if self
._spaceAttachments
: 
2906                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2910                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2911             elif physicalAttachment 
== 3: 
2913                 if self
._spaceAttachments
: 
2914                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2917                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2919                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2921             return self
._xpos
, self
._ypos
 
2925 class CircleShape(EllipseShape
): 
2926     """An EllipseShape whose width and height are the same.""" 
2927     def __init__(self
, diameter
): 
2928         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2929         self
.SetMaintainAspectRatio(True) 
2931     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2932         return FindEndForCircle(self
._width 
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
) 
2936 class TextShape(RectangleShape
): 
2937     """As wxRectangleShape, but only the text is displayed.""" 
2938     def __init__(self
, width
, height
): 
2939         RectangleShape
.__init
__(self
, width
, height
) 
2941     def OnDraw(self
, dc
): 
2946 class ShapeRegion(object): 
2947     """Object region.""" 
2948     def __init__(self
, region 
= None): 
2950             self
._regionText 
= region
._regionText
 
2951             self
._regionName 
= region
._regionName
 
2952             self
._textColour 
= region
._textColour
 
2954             self
._font 
= region
._font
 
2955             self
._minHeight 
= region
._minHeight
 
2956             self
._minWidth 
= region
._minWidth
 
2957             self
._width 
= region
._width
 
2958             self
._height 
= region
._height
 
2962             self
._regionProportionX 
= region
._regionProportionX
 
2963             self
._regionProportionY 
= region
._regionProportionY
 
2964             self
._formatMode 
= region
._formatMode
 
2965             self
._actualColourObject 
= region
._actualColourObject
 
2966             self
._actualPenObject 
= None 
2967             self
._penStyle 
= region
._penStyle
 
2968             self
._penColour 
= region
._penColour
 
2971             for line 
in region
._formattedText
: 
2972                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2973                 self
._formattedText
.append(new_line
) 
2975             self
._regionText 
= "" 
2976             self
._font 
= NormalFont
 
2977             self
._minHeight 
= 5.0 
2978             self
._minWidth 
= 5.0 
2984             self
._regionProportionX 
= -1.0 
2985             self
._regionProportionY 
= -1.0 
2986             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2987             self
._regionName 
= "" 
2988             self
._textColour 
= "BLACK" 
2989             self
._penColour 
= "BLACK" 
2990             self
._penStyle 
= wx
.SOLID
 
2991             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2992             self
._actualPenObject 
= None 
2994         self
._formattedText 
= [] 
2996     def ClearText(self
): 
2997         self
._formattedText 
= [] 
2999     def SetFont(self
, f
): 
3002     def SetMinSize(self
, w
, h
): 
3006     def SetSize(self
, w
, h
): 
3010     def SetPosition(self
, xp
, yp
): 
3014     def SetProportions(self
, xp
, yp
): 
3015         self
._regionProportionX 
= xp
 
3016         self
._regionProportionY 
= yp
 
3018     def SetFormatMode(self
, mode
): 
3019         self
._formatMode 
= mode
 
3021     def SetColour(self
, col
): 
3022         self
._textColour 
= col
 
3023         self
._actualColourObject 
= col
 
3025     def GetActualColourObject(self
): 
3026         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3027         return self
._actualColourObject
 
3029     def SetPenColour(self
, col
): 
3030         self
._penColour 
= col
 
3031         self
._actualPenObject 
= None 
3033     # Returns NULL if the pen is invisible 
3034     # (different to pen being transparent; indicates that 
3035     # region boundary should not be drawn.) 
3036     def GetActualPen(self
): 
3037         if self
._actualPenObject
: 
3038             return self
._actualPenObject
 
3040         if not self
._penColour
: 
3042         if self
._penColour
=="Invisible": 
3044         self
._actualPenObject 
= wx
.Pen(self
._penColour
, 1, self
._penStyle
) 
3045         return self
._actualPenObject
 
3047     def SetText(self
, s
): 
3048         self
._regionText 
= s
 
3050     def SetName(self
, s
): 
3051         self
._regionName 
= s
 
3054         return self
._regionText
 
3059     def GetMinSize(self
): 
3060         return self
._minWidth
, self
._minHeight
 
3062     def GetProportion(self
): 
3063         return self
._regionProportionX
, self
._regionProportionY
 
3066         return self
._width
, self
._height
 
3068     def GetPosition(self
): 
3069         return self
._x
, self
._y
 
3071     def GetFormatMode(self
): 
3072         return self
._formatMode
 
3075         return self
._regionName
 
3077     def GetColour(self
): 
3078         return self
._textColour
 
3080     def GetFormattedText(self
): 
3081         return self
._formattedText
 
3083     def GetPenColour(self
): 
3084         return self
._penColour
 
3086     def GetPenStyle(self
): 
3087         return self
._penStyle
 
3089     def SetPenStyle(self
, style
): 
3090         self
._penStyle 
= style
 
3091         self
._actualPenObject 
= None 
3096     def GetHeight(self
): 
3101 class ControlPoint(RectangleShape
): 
3102     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3103         RectangleShape
.__init
__(self
, size
, size
) 
3105         self
._canvas 
= theCanvas
 
3106         self
._shape 
= object 
3107         self
._xoffset 
= the_xoffset
 
3108         self
._yoffset 
= the_yoffset
 
3109         self
._type 
= the_type
 
3110         self
.SetPen(BlackForegroundPen
) 
3111         self
.SetBrush(wx
.BLACK_BRUSH
) 
3112         self
._oldCursor 
= None 
3113         self
._visible 
= True 
3114         self
._eraseObject 
= True 
3116     # Don't even attempt to draw any text - waste of time 
3117     def OnDrawContents(self
, dc
): 
3120     def OnDraw(self
, dc
): 
3121         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3122         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3123         RectangleShape
.OnDraw(self
, dc
) 
3125     def OnErase(self
, dc
): 
3126         RectangleShape
.OnErase(self
, dc
) 
3128     # Implement resizing of canvas object 
3129     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3130         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3132     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3133         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3135     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3136         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3138     def GetNumberOfAttachments(self
): 
3141     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3142         return self
._xpos
, self
._ypos
 
3144     def SetEraseObject(self
, er
): 
3145         self
._eraseObject 
= er
 
3148 class PolygonControlPoint(ControlPoint
): 
3149     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3150         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3151         self
._polygonVertex 
= vertex
 
3152         self
._originalDistance 
= 0.0 
3153         self
._newSize 
= wx
.RealPoint() 
3154         self
._originalSize 
= wx
.RealPoint() 
3156     def GetNewSize(self
): 
3157         return self
._newSize
 
3159     # Calculate what new size would be, at end of resize 
3160     def CalculateNewSize(self
, x
, y
): 
3161         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3162         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3164         self
._newSize
[0] = dist 
/ self
._originalDistance 
* self
._originalSize
[0] 
3165         self
._newSize
[1] = dist 
/ self
._originalDistance 
* self
._originalSize
[1] 
3167     # Implement resizing polygon or moving the vertex 
3168     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3169         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3171     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3172         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3174     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3175         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3177 from _canvas 
import * 
3178 from _lines 
import * 
3179 from _composit 
import *