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 *