1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
4 # Purpose: The basic OGL shapes
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
17 from _oglmisc
import *
23 global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
24 global BlackForegroundPen
, NormalFont
26 WhiteBackgroundPen
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)
27 WhiteBackgroundBrush
= wx
.Brush(wx
.WHITE
, wx
.SOLID
)
29 TransparentPen
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)
30 BlackForegroundPen
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
)
32 NormalFont
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)
39 class ShapeTextLine(object):
40 def __init__(self
, the_x
, the_y
, the_line
):
57 def SetText(self
, text
):
65 class ShapeEvtHandler(object):
66 def __init__(self
, prev
= None, shape
= None):
67 self
._previousHandler
= prev
68 self
._handlerShape
= shape
73 def SetShape(self
, sh
):
74 self
._handlerShape
= sh
77 return self
._handlerShape
79 def SetPreviousHandler(self
, handler
):
80 self
._previousHandler
= handler
82 def GetPreviousHandler(self
):
83 return self
._previousHandler
86 if self
!=self
.GetShape():
90 if self
._previousHandler
:
91 self
._previousHandler
.OnDraw(dc
)
93 def OnMoveLinks(self
, dc
):
94 if self
._previousHandler
:
95 self
._previousHandler
.OnMoveLinks(dc
)
97 def OnMoveLink(self
, dc
, moveControlPoints
= True):
98 if self
._previousHandler
:
99 self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
)
101 def OnDrawContents(self
, dc
):
102 if self
._previousHandler
:
103 self
._previousHandler
.OnDrawContents(dc
)
105 def OnDrawBranches(self
, dc
, erase
= False):
106 if self
._previousHandler
:
107 self
._previousHandler
.OnDrawBranches(dc
, erase
= erase
)
109 def OnSize(self
, x
, y
):
110 if self
._previousHandler
:
111 self
._previousHandler
.OnSize(x
, y
)
113 def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
114 if self
._previousHandler
:
115 return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
)
119 def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
120 if self
._previousHandler
:
121 return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
)
125 def OnErase(self
, dc
):
126 if self
._previousHandler
:
127 self
._previousHandler
.OnErase(dc
)
129 def OnEraseContents(self
, dc
):
130 if self
._previousHandler
:
131 self
._previousHandler
.OnEraseContents(dc
)
133 def OnHighlight(self
, dc
):
134 if self
._previousHandler
:
135 self
._previousHandler
.OnHighlight(dc
)
137 def OnLeftClick(self
, x
, y
, keys
, attachment
):
138 if self
._previousHandler
:
139 self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
)
141 def OnLeftDoubleClick(self
, x
, y
, keys
= 0, attachment
= 0):
142 if self
._previousHandler
:
143 self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
)
145 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
146 if self
._previousHandler
:
147 self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
)
149 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
150 if self
._previousHandler
:
151 self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
)
153 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
154 if self
._previousHandler
:
155 self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
)
157 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
158 if self
._previousHandler
:
159 self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
)
161 def OnDragRight(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
162 if self
._previousHandler
:
163 self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
)
165 def OnBeginDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
166 if self
._previousHandler
:
167 self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
)
169 def OnEndDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
170 if self
._previousHandler
:
171 self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
)
173 # Control points ('handles') redirect control to the actual shape,
174 # to make it easier to override sizing behaviour.
175 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
176 if self
._previousHandler
:
177 self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
)
179 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
180 if self
._previousHandler
:
181 self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
)
183 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
184 if self
._previousHandler
:
185 self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
)
187 def OnBeginSize(self
, w
, h
):
190 def OnEndSize(self
, w
, h
):
193 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
194 if self
._previousHandler
:
195 self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
)
197 def OnDrawControlPoints(self
, dc
):
198 if self
._previousHandler
:
199 self
._previousHandler
.OnDrawControlPoints(dc
)
201 def OnEraseControlPoints(self
, dc
):
202 if self
._previousHandler
:
203 self
._previousHandler
.OnEraseControlPoints(dc
)
205 # Can override this to prevent or intercept line reordering.
206 def OnChangeAttachment(self
, attachment
, line
, ordering
):
207 if self
._previousHandler
:
208 self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
)
212 class Shape(ShapeEvtHandler
):
217 The wxShape is the top-level, abstract object that all other objects
218 are derived from. All common functionality is represented by wxShape's
219 members, and overriden members that appear in derived classes and have
220 behaviour as documented for wxShape, are not documented separately.
223 GraphicsInSizeToContents
= False
225 def __init__(self
, canvas
= None):
226 ShapeEvtHandler
.__init
__(self
)
228 self
._eventHandler
= self
231 self
._formatted
= False
232 self
._canvas
= canvas
235 self
._pen
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
)
236 self
._brush
= wx
.WHITE_BRUSH
237 self
._font
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)
238 self
._textColour
= wx
.BLACK
239 self
._textColourName
= wx
.BLACK
240 self
._visible
= False
241 self
._selected
= False
242 self
._attachmentMode
= ATTACHMENT_MODE_NONE
243 self
._spaceAttachments
= True
244 self
._disableLabel
= False
245 self
._fixedWidth
= False
246 self
._fixedHeight
= False
247 self
._drawHandles
= True
248 self
._sensitivity
= OP_ALL
249 self
._draggable
= True
251 self
._formatMode
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
252 self
._shadowMode
= SHADOW_NONE
253 self
._shadowOffsetX
= 6
254 self
._shadowOffsetY
= 6
255 self
._shadowBrush
= wx
.BLACK_BRUSH
256 self
._textMarginX
= 5
257 self
._textMarginY
= 5
258 self
._regionName
= "0"
259 self
._centreResize
= True
260 self
._maintainAspectRatio
= False
261 self
._highlighted
= False
263 self
._branchNeckLength
= 10
264 self
._branchStemLength
= 10
265 self
._branchSpacing
= 10
266 self
._branchStyle
= BRANCHING_ATTACHMENT_NORMAL
270 self
._controlPoints
= []
271 self
._attachmentPoints
= []
275 # Set up a default region. Much of the above will be put into
276 # the region eventually (the duplication is for compatibility)
277 region
= ShapeRegion()
279 region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
))
280 region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
)
281 region
.SetColour("BLACK")
282 self
._regions
.append(region
)
285 return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__)
287 def GetClassName(self
):
288 return str(self
.__class
__).split(".")[-1][:-2]
292 i
= self
._parent
.GetChildren().index(self
)
293 self
._parent
.GetChildren(i
).remove(self
)
297 self
.ClearAttachments()
299 self
._handlerShape
= None
302 self
.RemoveFromCanvas(self
._canvas
)
304 self
.GetEventHandler().OnDelete()
305 self
._eventHandler
= None
308 ShapeEvtHandler
.__del
__(self
)
311 """TRUE if the shape may be dragged by the user."""
314 def SetShape(self
, sh
):
315 self
._handlerShape
= sh
318 """Get the internal canvas."""
321 def GetBranchStyle(self
):
322 return self
._branchStyle
324 def GetRotation(self
):
325 """Return the angle of rotation in radians."""
326 return self
._rotation
328 def SetRotation(self
, rotation
):
329 self
._rotation
= rotation
331 def SetHighlight(self
, hi
, recurse
= False):
332 """Set the highlight for a shape. Shape highlighting is unimplemented."""
333 self
._highlighted
= hi
335 for shape
in self
._children
:
336 shape
.SetHighlight(hi
, recurse
)
338 def SetSensitivityFilter(self
, sens
= OP_ALL
, recursive
= False):
339 """Set the shape to be sensitive or insensitive to specific mouse
342 sens is a bitlist of the following:
348 * OP_ALL (equivalent to a combination of all the above).
350 self
._draggable
= sens
& OP_DRAG_LEFT
352 self
._sensitivity
= sens
354 for shape
in self
._children
:
355 shape
.SetSensitivityFilter(sens
, True)
357 def SetDraggable(self
, drag
, recursive
= False):
358 """Set the shape to be draggable or not draggable."""
359 self
._draggable
= drag
361 self
._sensitivity |
= OP_DRAG_LEFT
362 elif self
._sensitivity
& OP_DRAG_LEFT
:
363 self
._sensitivity
-= OP_DRAG_LEFT
366 for shape
in self
._children
:
367 shape
.SetDraggable(drag
, True)
369 def SetDrawHandles(self
, drawH
):
370 """Set the drawHandles flag for this shape and all descendants.
371 If drawH is TRUE (the default), any handles (control points) will
372 be drawn. Otherwise, the handles will not be drawn.
374 self
._drawHandles
= drawH
375 for shape
in self
._children
:
376 shape
.SetDrawHandles(drawH
)
378 def SetShadowMode(self
, mode
, redraw
= False):
379 """Set the shadow mode (whether a shadow is drawn or not).
380 mode can be one of the following:
383 No shadow (the default).
385 Shadow on the left side.
387 Shadow on the right side.
389 if redraw
and self
.GetCanvas():
390 dc
= wx
.ClientDC(self
.GetCanvas())
391 self
.GetCanvas().PrepareDC(dc
)
393 self
._shadowMode
= mode
396 self
._shadowMode
= mode
398 def SetCanvas(self
, theCanvas
):
399 """Identical to Shape.Attach."""
400 self
._canvas
= theCanvas
401 for shape
in self
._children
:
402 shape
.SetCanvas(theCanvas
)
404 def AddToCanvas(self
, theCanvas
, addAfter
= None):
405 """Add the shape to the canvas's shape list.
406 If addAfter is non-NULL, will add the shape after this one.
408 theCanvas
.AddShape(self
, addAfter
)
411 for object in self
._children
:
412 object.AddToCanvas(theCanvas
, lastImage
)
415 def InsertInCanvas(self
, theCanvas
):
416 """Insert the shape at the front of the shape list of canvas."""
417 theCanvas
.InsertShape(self
)
420 for object in self
._children
:
421 object.AddToCanvas(theCanvas
, lastImage
)
424 def RemoveFromCanvas(self
, theCanvas
):
425 """Remove the shape from the canvas."""
430 theCanvas
.RemoveShape(self
)
431 for object in self
._children
:
432 object.RemoveFromCanvas(theCanvas
)
434 def ClearAttachments(self
):
435 """Clear internal custom attachment point shapes (of class
438 self
._attachmentPoints
= []
440 def ClearText(self
, regionId
= 0):
441 """Clear the text from the specified text region."""
444 if regionId
< len(self
._regions
):
445 self
._regions
[regionId
].ClearText()
447 def ClearRegions(self
):
448 """Clear the ShapeRegions from the shape."""
451 def AddRegion(self
, region
):
452 """Add a region to the shape."""
453 self
._regions
.append(region
)
455 def SetDefaultRegionSize(self
):
456 """Set the default region to be consistent with the shape size."""
457 if not self
._regions
:
459 w
, h
= self
.GetBoundingBoxMax()
460 self
._regions
[0].SetSize(w
, h
)
462 def HitTest(self
, x
, y
):
463 """Given a point on a canvas, returns TRUE if the point was on the
464 shape, and returns the nearest attachment point and distance from
465 the given point and target.
467 width
, height
= self
.GetBoundingBoxMax()
473 width
+= 4 # Allowance for inaccurate mousing
476 left
= self
._xpos
- width
/ 2.0
477 top
= self
._ypos
- height
/ 2.0
478 right
= self
._xpos
+ width
/ 2.0
479 bottom
= self
._ypos
+ height
/ 2.0
481 nearest_attachment
= 0
483 # If within the bounding box, check the attachment points
485 if x
>= left
and x
<= right
and y
>= top
and y
<= bottom
:
486 n
= self
.GetNumberOfAttachments()
489 # GetAttachmentPosition[Edge] takes a logical attachment position,
490 # i.e. if it's rotated through 90%, position 0 is East-facing.
493 e
= self
.GetAttachmentPositionEdge(i
)
496 l
= math
.sqrt(((xp
- x
) * (xp
- x
)) + (yp
- y
) * (yp
- y
))
499 nearest_attachment
= i
501 return nearest_attachment
, nearest
504 # Format a text string according to the region size, adding
505 # strings with positions to region text list
507 def FormatText(self
, dc
, s
, i
= 0):
508 """Reformat the given text region; defaults to formatting the
513 if not self
._regions
:
516 if i
> len(self
._regions
):
519 region
= self
._regions
[i
]
520 region
._regionText
= s
521 dc
.SetFont(region
.GetFont())
523 w
, h
= region
.GetSize()
525 stringList
= FormatText(dc
, s
, (w
- 2 * self
._textMarginX
), (h
- 2 * self
._textMarginY
), region
.GetFormatMode())
527 line
= ShapeTextLine(0.0, 0.0, s
)
528 region
.GetFormattedText().append(line
)
532 # Don't try to resize an object with more than one image (this
533 # case should be dealt with by overriden handlers)
534 if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
535 len(region
.GetFormattedText()) and \
536 len(self
._regions
) == 1 and \
537 not Shape
.GraphicsInSizeToContents
:
539 actualW
, actualH
= GetCentredTextExtent(dc
, region
.GetFormattedText())
540 if actualW
+ 2 * self
._textMarginX
!= w
or actualH
+ 2 * self
._textMarginY
!= h
:
541 # If we are a descendant of a composite, must make sure
542 # the composite gets resized properly
544 topAncestor
= self
.GetTopAncestor()
545 if topAncestor
!= self
:
546 Shape
.GraphicsInSizeToContents
= True
548 composite
= topAncestor
550 self
.SetSize(actualW
+ 2 * self
._textMarginX
, actualH
+ 2 * self
._textMarginY
)
551 self
.Move(dc
, self
._xpos
, self
._ypos
)
552 composite
.CalculateSize()
553 if composite
.Selected():
554 composite
.DeleteControlPoints(dc
)
555 composite
.MakeControlPoints()
556 composite
.MakeMandatoryControlPoints()
557 # Where infinite recursion might happen if we didn't stop it
559 Shape
.GraphicsInSizeToContents
= False
563 self
.SetSize(actualW
+ 2 * self
._textMarginX
, actualH
+ 2 * self
._textMarginY
)
564 self
.Move(dc
, self
._xpos
, self
._ypos
)
565 self
.EraseContents(dc
)
566 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
- 2 * self
._textMarginX
, actualH
- 2 * self
._textMarginY
, region
.GetFormatMode())
567 self
._formatted
= True
569 def Recentre(self
, dc
):
570 """Do recentring (or other formatting) for all the text regions
573 w
, h
= self
.GetBoundingBoxMin()
574 for region
in self
._regions
:
575 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
- 2 * self
._textMarginX
, h
- 2 * self
._textMarginY
, region
.GetFormatMode())
577 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
578 """Get the point at which the line from (x1, y1) to (x2, y2) hits
579 the shape. Returns False if the line doesn't hit the perimeter.
583 def SetPen(self
, the_pen
):
584 """Set the pen for drawing the shape's outline."""
587 def SetBrush(self
, the_brush
):
588 """Set the brush for filling the shape's shape."""
589 self
._brush
= the_brush
591 # Get the top - most (non-division) ancestor, or self
592 def GetTopAncestor(self
):
593 """Return the top-most ancestor of this shape (the root of
596 if not self
.GetParent():
599 if isinstance(self
.GetParent(), DivisionShape
):
601 return self
.GetParent().GetTopAncestor()
604 def SetFont(self
, the_font
, regionId
= 0):
605 """Set the font for the specified text region."""
606 self
._font
= the_font
607 if regionId
< len(self
._regions
):
608 self
._regions
[regionId
].SetFont(the_font
)
610 def GetFont(self
, regionId
= 0):
611 """Get the font for the specified text region."""
612 if regionId
>= len(self
._regions
):
614 return self
._regions
[regionId
].GetFont()
616 def SetFormatMode(self
, mode
, regionId
= 0):
617 """Set the format mode of the default text region. The argument
618 can be a bit list of the following:
627 if regionId
< len(self
._regions
):
628 self
._regions
[regionId
].SetFormatMode(mode
)
630 def GetFormatMode(self
, regionId
= 0):
631 if regionId
>= len(self
._regions
):
633 return self
._regions
[regionId
].GetFormatMode()
635 def SetTextColour(self
, the_colour
, regionId
= 0):
636 """Set the colour for the specified text region."""
637 self
._textColour
= wx
.TheColourDatabase
.Find(the_colour
)
638 self
._textColourName
= the_colour
640 if regionId
< len(self
._regions
):
641 self
._regions
[regionId
].SetColour(the_colour
)
643 def GetTextColour(self
, regionId
= 0):
644 """Get the colour for the specified text region."""
645 if regionId
>= len(self
._regions
):
647 return self
._regions
[regionId
].GetTextColour()
649 def SetRegionName(self
, name
, regionId
= 0):
650 """Set the name for this region.
651 The name for a region is unique within the scope of the whole
652 composite, whereas a region id is unique only for a single image.
654 if regionId
< len(self
._regions
):
655 self
._regions
[regionId
].SetName(name
)
657 def GetRegionName(self
, regionId
= 0):
658 """Get the region's name.
659 A region's name can be used to uniquely determine a region within
660 an entire composite image hierarchy. See also Shape.SetRegionName.
662 if regionId
>= len(self
._regions
):
664 return self
._regions
[regionId
].GetName()
666 def GetRegionId(self
, name
):
667 """Get the region's identifier by name.
668 This is not unique for within an entire composite, but is unique
671 for i
, r
in enumerate(self
._regions
):
672 if r
.GetName() == name
:
676 # Name all _regions in all subimages recursively
677 def NameRegions(self
, parentName
=""):
678 """Make unique names for all the regions in a shape or composite shape."""
679 n
= self
.GetNumberOfTextRegions()
682 buff
= parentName
+"."+str(i
)
685 self
.SetRegionName(buff
, i
)
687 for j
, child
in enumerate(self
._children
):
689 buff
= parentName
+"."+str(j
)
692 child
.NameRegions(buff
)
694 # Get a region by name, possibly looking recursively into composites
695 def FindRegion(self
, name
):
696 """Find the actual image ('this' if non-composite) and region id
697 for the given region name.
699 id = self
.GetRegionId(name
)
703 for child
in self
._children
:
704 actualImage
, regionId
= child
.FindRegion(name
)
706 return actualImage
, regionId
710 # Finds all region names for this image (composite or simple).
711 def FindRegionNames(self
):
712 """Get a list of all region names for this image (composite or simple)."""
714 n
= self
.GetNumberOfTextRegions()
716 list.append(self
.GetRegionName(i
))
718 for child
in self
._children
:
719 list += child
.FindRegionNames()
723 def AssignNewIds(self
):
724 """Assign new ids to this image and its children."""
725 self
._id
= wx
.NewId()
726 for child
in self
._children
:
729 def OnDraw(self
, dc
):
732 def OnMoveLinks(self
, dc
):
733 # Want to set the ends of all attached links
734 # to point to / from this object
736 for line
in self
._lines
:
737 line
.GetEventHandler().OnMoveLink(dc
)
739 def OnDrawContents(self
, dc
):
740 if not self
._regions
:
743 bound_x
, bound_y
= self
.GetBoundingBoxMin()
748 region
= self
._regions
[0]
750 dc
.SetFont(region
.GetFont())
752 dc
.SetTextForeground(region
.GetActualColourObject())
753 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
754 if not self
._formatted
:
755 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x
- 2 * self
._textMarginX
, bound_y
- 2 * self
._textMarginY
, region
.GetFormatMode())
756 self
._formatted
= True
758 if not self
.GetDisableLabel():
759 DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x
- 2 * self
._textMarginX
, bound_y
- 2 * self
._textMarginY
, region
.GetFormatMode())
762 def DrawContents(self
, dc
):
763 """Draw the internal graphic of the shape (such as text).
765 Do not override this function: override OnDrawContents, which
766 is called by this function.
768 self
.GetEventHandler().OnDrawContents(dc
)
770 def OnSize(self
, x
, y
):
773 def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
776 def OnErase(self
, dc
):
777 if not self
._visible
:
781 for line
in self
._lines
:
782 line
.GetEventHandler().OnErase(dc
)
784 self
.GetEventHandler().OnEraseContents(dc
)
786 def OnEraseContents(self
, dc
):
787 if not self
._visible
:
790 xp
, yp
= self
.GetX(), self
.GetY()
791 minX
, minY
= self
.GetBoundingBoxMin()
792 maxX
, maxY
= self
.GetBoundingBoxMax()
794 topLeftX
= xp
- maxX
/ 2.0 - 2
795 topLeftY
= yp
- maxY
/ 2.0 - 2
799 penWidth
= self
._pen
.GetWidth()
801 dc
.SetPen(self
.GetBackgroundPen())
802 dc
.SetBrush(self
.GetBackgroundBrush())
804 dc
.DrawRectangle(topLeftX
- penWidth
, topLeftY
- penWidth
, maxX
+ penWidth
* 2 + 4, maxY
+ penWidth
* 2 + 4)
806 def EraseLinks(self
, dc
, attachment
= -1, recurse
= False):
807 """Erase links attached to this shape, but do not repair damage
808 caused to other shapes.
810 if not self
._visible
:
813 for line
in self
._lines
:
814 if attachment
== -1 or (line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
):
815 line
.GetEventHandler().OnErase(dc
)
818 for child
in self
._children
:
819 child
.EraseLinks(dc
, attachment
, recurse
)
821 def DrawLinks(self
, dc
, attachment
= -1, recurse
= False):
822 """Draws any lines linked to this shape."""
823 if not self
._visible
:
826 for line
in self
._lines
:
827 if attachment
== -1 or (line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
):
831 for child
in self
._children
:
832 child
.DrawLinks(dc
, attachment
, recurse
)
834 # Returns TRUE if pt1 <= pt2 in the sense that one point comes before
835 # another on an edge of the shape.
836 # attachmentPoint is the attachment point (= side) in question.
838 # This is the default, rectangular implementation.
839 def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
):
840 """Return TRUE if pt1 is less than or equal to pt2, in the sense
841 that one point comes before another on an edge of the shape.
843 attachment is the attachment point (side) in question.
845 This function is used in Shape.MoveLineToNewAttachment to determine
846 the new line ordering.
848 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachmentPoint
)
849 if physicalAttachment
in [0, 2]:
850 return pt1
[0] <= pt2
[0]
851 elif physicalAttachment
in [1, 3]:
852 return pt1
[1] <= pt2
[1]
856 def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
):
857 """Move the given line (which must already be attached to the shape)
858 to a different attachment point on the shape, or a different order
859 on the same attachment.
861 Calls Shape.AttachmentSortTest and then
862 ShapeEvtHandler.OnChangeAttachment.
864 if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
:
867 # Is (x, y) on this object? If so, find the new attachment point
868 # the user has moved the point to
869 hit
= self
.HitTest(x
, y
)
873 newAttachment
, distance
= hit
877 if to_move
.GetTo() == self
:
878 oldAttachment
= to_move
.GetAttachmentTo()
880 oldAttachment
= to_move
.GetAttachmentFrom()
882 # The links in a new ordering
883 # First, add all links to the new list
884 newOrdering
= self
._lines
[:]
886 # Delete the line object from the list of links; we're going to move
887 # it to another position in the list
888 del newOrdering
[newOrdering
.index(to_move
)]
895 for line
in newOrdering
:
896 if line
.GetTo() == self
and oldAttachment
== line
.GetAttachmentTo() or \
897 line
.GetFrom() == self
and oldAttachment
== line
.GetAttachmentFrom():
898 startX
, startY
, endX
, endY
= line
.GetEnds()
899 if line
.GetTo() == self
:
906 thisPoint
= wx
.RealPoint(xp
, yp
)
907 lastPoint
= wx
.RealPoint(old_x
, old_y
)
908 newPoint
= wx
.RealPoint(x
, y
)
910 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
):
912 newOrdering
.insert(newOrdering
.index(line
), to_move
)
920 newOrdering
.append(to_move
)
922 self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
)
925 def OnChangeAttachment(self
, attachment
, line
, ordering
):
926 if line
.GetTo() == self
:
927 line
.SetAttachmentTo(attachment
)
929 line
.SetAttachmentFrom(attachment
)
931 self
.ApplyAttachmentOrdering(ordering
)
933 dc
= wx
.ClientDC(self
.GetCanvas())
934 self
.GetCanvas().PrepareDC(dc
)
937 if not self
.GetCanvas().GetQuickEditMode():
938 self
.GetCanvas().Redraw(dc
)
940 # Reorders the lines according to the given list
941 def ApplyAttachmentOrdering(self
, linesToSort
):
942 """Apply the line ordering in linesToSort to the shape, to reorder
943 the way lines are attached.
945 linesStore
= self
._lines
[:]
949 for line
in linesToSort
:
950 if line
in linesStore
:
951 del linesStore
[linesStore
.index(line
)]
952 self
._lines
.append(line
)
954 # Now add any lines that haven't been listed in linesToSort
955 self
._lines
+= linesStore
957 def SortLines(self
, attachment
, linesToSort
):
958 """ Reorder the lines coming into the node image at this attachment
959 position, in the order in which they appear in linesToSort.
961 Any remaining lines not in the list will be added to the end.
963 # This is a temporary store of all the lines at this attachment
964 # point. We'll tick them off as we've processed them.
965 linesAtThisAttachment
= []
967 for line
in self
._lines
[:]:
968 if line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or \
969 line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
:
970 linesAtThisAttachment
.append(line
)
971 del self
._lines
[self
._lines
.index(line
)]
973 for line
in linesToSort
:
974 if line
in linesAtThisAttachment
:
976 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)]
977 self
._lines
.append(line
)
979 # Now add any lines that haven't been listed in linesToSort
980 self
._lines
+= linesAtThisAttachment
982 def OnHighlight(self
, dc
):
985 def OnLeftClick(self
, x
, y
, keys
= 0, attachment
= 0):
986 if self
._sensitivity
& OP_CLICK_LEFT
!= OP_CLICK_LEFT
:
988 attachment
, dist
= self
._parent
.HitTest(x
, y
)
989 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
)
991 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
992 if self
._sensitivity
& OP_CLICK_RIGHT
!= OP_CLICK_RIGHT
:
993 attachment
, dist
= self
._parent
.HitTest(x
, y
)
994 self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
)
996 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
997 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
999 hit
= self
._parent
.HitTest(x
, y
)
1001 attachment
, dist
= hit
1002 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
)
1005 dc
= wx
.ClientDC(self
.GetCanvas())
1006 self
.GetCanvas().PrepareDC(dc
)
1007 dc
.SetLogicalFunction(OGLRBLF
)
1009 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
1010 dc
.SetPen(dottedPen
)
1011 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
1013 xx
= x
+ DragOffsetX
1014 yy
= y
+ DragOffsetY
1016 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1017 w
, h
= self
.GetBoundingBoxMax()
1018 self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
)
1020 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1021 global DragOffsetX
, DragOffsetY
1023 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1025 hit
= self
._parent
.HitTest(x
, y
)
1027 attachment
, dist
= hit
1028 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
)
1031 DragOffsetX
= self
._xpos
- x
1032 DragOffsetY
= self
._ypos
- y
1034 dc
= wx
.ClientDC(self
.GetCanvas())
1035 self
.GetCanvas().PrepareDC(dc
)
1037 # New policy: don't erase shape until end of drag.
1039 xx
= x
+ DragOffsetX
1040 yy
= y
+ DragOffsetY
1041 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1042 dc
.SetLogicalFunction(OGLRBLF
)
1044 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
1045 dc
.SetPen(dottedPen
)
1046 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
1048 w
, h
= self
.GetBoundingBoxMax()
1049 self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
)
1050 self
._canvas
.CaptureMouse()
1052 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1053 if self
._canvas
.HasCapture():
1054 self
._canvas
.ReleaseMouse()
1055 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1057 hit
= self
._parent
.HitTest(x
, y
)
1059 attachment
, dist
= hit
1060 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
)
1063 dc
= wx
.ClientDC(self
.GetCanvas())
1064 self
.GetCanvas().PrepareDC(dc
)
1066 dc
.SetLogicalFunction(wx
.COPY
)
1067 xx
= x
+ DragOffsetX
1068 yy
= y
+ DragOffsetY
1069 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1071 # New policy: erase shape at end of drag.
1074 self
.Move(dc
, xx
, yy
)
1075 if self
._canvas
and not self
._canvas
.GetQuickEditMode():
1076 self
._canvas
.Redraw(dc
)
1078 def OnDragRight(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
1079 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1081 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1082 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
)
1085 def OnBeginDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
1086 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1088 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1089 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
)
1092 def OnEndDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
1093 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1095 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1096 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
)
1099 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
1100 points
= [[x
- w
/ 2.0, y
- h
/ 2.0],
1101 [x
+ w
/ 2.0, y
- h
/ 2.0],
1102 [x
+ w
/ 2.0, y
+ h
/ 2.0],
1103 [x
- w
/ 2.0, y
+ h
/ 2.0],
1104 [x
- w
/ 2.0, y
- h
/ 2.0],
1107 dc
.DrawLines(points
)
1109 def Attach(self
, can
):
1110 """Set the shape's internal canvas pointer to point to the given canvas."""
1114 """Disassociates the shape from its canvas."""
1117 def Move(self
, dc
, x
, y
, display
= True):
1118 """Move the shape to the given position.
1119 Redraw if display is TRUE.
1124 if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
):
1127 self
._xpos
, self
._ypos
= x
, y
1129 self
.ResetControlPoints()
1136 self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
)
1138 def MoveLinks(self
, dc
):
1139 """Redraw all the lines attached to the shape."""
1140 self
.GetEventHandler().OnMoveLinks(dc
)
1143 """Draw the whole shape and any lines attached to it.
1145 Do not override this function: override OnDraw, which is called
1149 self
.GetEventHandler().OnDraw(dc
)
1150 self
.GetEventHandler().OnDrawContents(dc
)
1151 self
.GetEventHandler().OnDrawControlPoints(dc
)
1152 self
.GetEventHandler().OnDrawBranches(dc
)
1155 """Flash the shape."""
1156 if self
.GetCanvas():
1157 dc
= wx
.ClientDC(self
.GetCanvas())
1158 self
.GetCanvas
.PrepareDC(dc
)
1160 dc
.SetLogicalFunction(OGLRBLF
)
1162 dc
.SetLogicalFunction(wx
.COPY
)
1165 def Show(self
, show
):
1166 """Set a flag indicating whether the shape should be drawn."""
1167 self
._visible
= show
1168 for child
in self
._children
:
1171 def Erase(self
, dc
):
1173 Does not repair damage caused to other shapes.
1175 self
.GetEventHandler().OnErase(dc
)
1176 self
.GetEventHandler().OnEraseControlPoints(dc
)
1177 self
.GetEventHandler().OnDrawBranches(dc
, erase
= True)
1179 def EraseContents(self
, dc
):
1180 """Erase the shape contents, that is, the area within the shape's
1181 minimum bounding box.
1183 self
.GetEventHandler().OnEraseContents(dc
)
1185 def AddText(self
, string
):
1186 """Add a line of text to the shape's default text region."""
1187 if not self
._regions
:
1190 region
= self
._regions
[0]
1192 new_line
= ShapeTextLine(0, 0, string
)
1193 text
= region
.GetFormattedText()
1194 text
.append(new_line
)
1196 self
._formatted
= False
1198 def SetSize(self
, x
, y
, recursive
= True):
1199 """Set the shape's size."""
1200 self
.SetAttachmentSize(x
, y
)
1201 self
.SetDefaultRegionSize()
1203 def SetAttachmentSize(self
, w
, h
):
1204 width
, height
= self
.GetBoundingBoxMin()
1208 scaleX
= float(w
) / width
1212 scaleY
= float(h
) / height
1214 for point
in self
._attachmentPoints
:
1215 point
._x
= point
._x
* scaleX
1216 point
._y
= point
._y
* scaleY
1218 # Add line FROM this object
1219 def AddLine(self
, line
, other
, attachFrom
= 0, attachTo
= 0, positionFrom
= -1, positionTo
= -1):
1220 """Add a line between this shape and the given other shape, at the
1221 specified attachment points.
1223 The position in the list of lines at each end can also be specified,
1224 so that the line will be drawn at a particular point on its attachment
1227 if positionFrom
== -1:
1228 if not line
in self
._lines
:
1229 self
._lines
.append(line
)
1231 # Don't preserve old ordering if we have new ordering instructions
1233 self
._lines
.remove(line
)
1236 if positionFrom
< len(self
._lines
):
1237 self
._lines
.insert(positionFrom
, line
)
1239 self
._lines
.append(line
)
1241 if positionTo
== -1:
1242 if not other
in other
._lines
:
1243 other
._lines
.append(line
)
1245 # Don't preserve old ordering if we have new ordering instructions
1247 other
._lines
.remove(line
)
1250 if positionTo
< len(other
._lines
):
1251 other
._lines
.insert(positionTo
, line
)
1253 other
._lines
.append(line
)
1257 line
.SetAttachments(attachFrom
, attachTo
)
1259 dc
= wx
.ClientDC(self
._canvas
)
1260 self
._canvas
.PrepareDC(dc
)
1263 def RemoveLine(self
, line
):
1264 """Remove the given line from the shape's list of attached lines."""
1265 if line
.GetFrom() == self
:
1266 line
.GetTo()._lines
.remove(line
)
1268 line
.GetFrom()._lines
.remove(line
)
1270 self
._lines
.remove(line
)
1272 # Default - make 6 control points
1273 def MakeControlPoints(self
):
1274 """Make a list of control points (draggable handles) appropriate to
1277 maxX
, maxY
= self
.GetBoundingBoxMax()
1278 minX
, minY
= self
.GetBoundingBoxMin()
1280 widthMin
= minX
+ CONTROL_POINT_SIZE
+ 2
1281 heightMin
= minY
+ CONTROL_POINT_SIZE
+ 2
1283 # Offsets from main object
1284 top
= -heightMin
/ 2.0
1285 bottom
= heightMin
/ 2.0 + (maxY
- minY
)
1286 left
= -widthMin
/ 2.0
1287 right
= widthMin
/ 2.0 + (maxX
- minX
)
1289 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
)
1290 self
._canvas
.AddShape(control
)
1291 self
._controlPoints
.append(control
)
1293 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
)
1294 self
._canvas
.AddShape(control
)
1295 self
._controlPoints
.append(control
)
1297 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
)
1298 self
._canvas
.AddShape(control
)
1299 self
._controlPoints
.append(control
)
1301 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
)
1302 self
._canvas
.AddShape(control
)
1303 self
._controlPoints
.append(control
)
1305 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
)
1306 self
._canvas
.AddShape(control
)
1307 self
._controlPoints
.append(control
)
1309 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
)
1310 self
._canvas
.AddShape(control
)
1311 self
._controlPoints
.append(control
)
1313 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
)
1314 self
._canvas
.AddShape(control
)
1315 self
._controlPoints
.append(control
)
1317 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
)
1318 self
._canvas
.AddShape(control
)
1319 self
._controlPoints
.append(control
)
1321 def MakeMandatoryControlPoints(self
):
1322 """Make the mandatory control points.
1324 For example, the control point on a dividing line should appear even
1325 if the divided rectangle shape's handles should not appear (because
1326 it is the child of a composite, and children are not resizable).
1328 for child
in self
._children
:
1329 child
.MakeMandatoryControlPoints()
1331 def ResetMandatoryControlPoints(self
):
1332 """Reset the mandatory control points."""
1333 for child
in self
._children
:
1334 child
.ResetMandatoryControlPoints()
1336 def ResetControlPoints(self
):
1337 """Reset the positions of the control points (for instance when the
1338 shape's shape has changed).
1340 self
.ResetMandatoryControlPoints()
1342 if len(self
._controlPoints
) == 0:
1345 maxX
, maxY
= self
.GetBoundingBoxMax()
1346 minX
, minY
= self
.GetBoundingBoxMin()
1348 widthMin
= minX
+ CONTROL_POINT_SIZE
+ 2
1349 heightMin
= minY
+ CONTROL_POINT_SIZE
+ 2
1351 # Offsets from main object
1352 top
= -heightMin
/ 2.0
1353 bottom
= heightMin
/ 2.0 + (maxY
- minY
)
1354 left
= -widthMin
/ 2.0
1355 right
= widthMin
/ 2.0 + (maxX
- minX
)
1357 self
._controlPoints
[0]._xoffset
= left
1358 self
._controlPoints
[0]._yoffset
= top
1360 self
._controlPoints
[1]._xoffset
= 0
1361 self
._controlPoints
[1]._yoffset
= top
1363 self
._controlPoints
[2]._xoffset
= right
1364 self
._controlPoints
[2]._yoffset
= top
1366 self
._controlPoints
[3]._xoffset
= right
1367 self
._controlPoints
[3]._yoffset
= 0
1369 self
._controlPoints
[4]._xoffset
= right
1370 self
._controlPoints
[4]._yoffset
= bottom
1372 self
._controlPoints
[5]._xoffset
= 0
1373 self
._controlPoints
[5]._yoffset
= bottom
1375 self
._controlPoints
[6]._xoffset
= left
1376 self
._controlPoints
[6]._yoffset
= bottom
1378 self
._controlPoints
[7]._xoffset
= left
1379 self
._controlPoints
[7]._yoffset
= 0
1381 def DeleteControlPoints(self
, dc
= None):
1382 """Delete the control points (or handles) for the shape.
1384 Does not redraw the shape.
1386 for control
in self
._controlPoints
[:]:
1388 control
.GetEventHandler().OnErase(dc
)
1390 self
._controlPoints
.remove(control
)
1391 self
._controlPoints
= []
1393 # Children of divisions are contained objects,
1395 if not isinstance(self
, DivisionShape
):
1396 for child
in self
._children
:
1397 child
.DeleteControlPoints(dc
)
1399 def OnDrawControlPoints(self
, dc
):
1400 if not self
._drawHandles
:
1403 dc
.SetBrush(wx
.BLACK_BRUSH
)
1404 dc
.SetPen(wx
.BLACK_PEN
)
1406 for control
in self
._controlPoints
:
1409 # Children of divisions are contained objects,
1411 # This test bypasses the type facility for speed
1412 # (critical when drawing)
1414 if not isinstance(self
, DivisionShape
):
1415 for child
in self
._children
:
1416 child
.GetEventHandler().OnDrawControlPoints(dc
)
1418 def OnEraseControlPoints(self
, dc
):
1419 for control
in self
._controlPoints
:
1422 if not isinstance(self
, DivisionShape
):
1423 for child
in self
._children
:
1424 child
.GetEventHandler().OnEraseControlPoints(dc
)
1426 def Select(self
, select
, dc
= None):
1427 """Select or deselect the given shape, drawing or erasing control points
1428 (handles) as necessary.
1430 self
._selected
= select
1432 self
.MakeControlPoints()
1433 # Children of divisions are contained objects,
1435 if not isinstance(self
, DivisionShape
):
1436 for child
in self
._children
:
1437 child
.MakeMandatoryControlPoints()
1439 self
.GetEventHandler().OnDrawControlPoints(dc
)
1441 self
.DeleteControlPoints(dc
)
1442 if not isinstance(self
, DivisionShape
):
1443 for child
in self
._children
:
1444 child
.DeleteControlPoints(dc
)
1447 """TRUE if the shape is currently selected."""
1448 return self
._selected
1450 def AncestorSelected(self
):
1451 """TRUE if the shape's ancestor is currently selected."""
1454 if not self
.GetParent():
1456 return self
.GetParent().AncestorSelected()
1458 def GetNumberOfAttachments(self
):
1459 """Get the number of attachment points for this shape."""
1460 # Should return the MAXIMUM attachment point id here,
1461 # so higher-level functions can iterate through all attachments,
1462 # even if they're not contiguous.
1464 if len(self
._attachmentPoints
) == 0:
1468 for point
in self
._attachmentPoints
:
1469 if point
._id
> maxN
:
1473 def AttachmentIsValid(self
, attachment
):
1474 """TRUE if attachment is a valid attachment point."""
1475 if len(self
._attachmentPoints
) == 0:
1476 return attachment
in range(4)
1478 for point
in self
._attachmentPoints
:
1479 if point
._id
== attachment
:
1483 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
1484 """Get the position at which the given attachment point should be drawn.
1486 If attachment isn't found among the attachment points of the shape,
1489 if self
._attachmentMode
== ATTACHMENT_MODE_NONE
:
1490 return self
._xpos
, self
._ypos
1491 elif self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
1492 pt
, stemPt
= self
.GetBranchingAttachmentPoint(attachment
, nth
)
1494 elif self
._attachmentMode
== ATTACHMENT_MODE_EDGE
:
1495 if len(self
._attachmentPoints
):
1496 for point
in self
._attachmentPoints
:
1497 if point
._id
== attachment
:
1498 return self
._xpos
+ point
._x
, self
._ypos
+ point
._y
1501 # Assume is rectangular
1502 w
, h
= self
.GetBoundingBoxMax()
1503 top
= self
._ypos
+ h
/ 2.0
1504 bottom
= self
._ypos
- h
/ 2.0
1505 left
= self
._xpos
- w
/ 2.0
1506 right
= self
._xpos
+ w
/ 2.0
1509 line
and line
.IsEnd(self
)
1511 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1514 if physicalAttachment
== 0:
1515 pt
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
)
1516 elif physicalAttachment
== 1:
1517 pt
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
)
1518 elif physicalAttachment
== 2:
1519 pt
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
)
1520 elif physicalAttachment
== 3:
1521 pt
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
)
1527 def GetBoundingBoxMax(self
):
1528 """Get the maximum bounding box for the shape, taking into account
1529 external features such as shadows.
1531 ww
, hh
= self
.GetBoundingBoxMin()
1532 if self
._shadowMode
!= SHADOW_NONE
:
1533 ww
+= self
._shadowOffsetX
1534 hh
+= self
._shadowOffsetY
1537 def GetBoundingBoxMin(self
):
1538 """Get the minimum bounding box for the shape, that defines the area
1539 available for drawing the contents (such as text).
1545 def HasDescendant(self
, image
):
1546 """TRUE if image is a descendant of this composite."""
1549 for child
in self
._children
:
1550 if child
.HasDescendant(image
):
1554 # Assuming the attachment lies along a vertical or horizontal line,
1555 # calculate the position on that point.
1556 def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
):
1557 """Assuming the attachment lies along a vertical or horizontal line,
1558 calculate the position on that point.
1563 The first point of the line repesenting the edge of the shape.
1566 The second point of the line representing the edge of the shape.
1569 The position on the edge (for example there may be 6 lines at
1570 this attachment point, and this may be the 2nd line.
1573 The number of lines at this edge.
1580 This function expects the line to be either vertical or horizontal,
1581 and determines which.
1583 isEnd
= line
and line
.IsEnd(self
)
1585 # Are we horizontal or vertical?
1586 isHorizontal
= RoughlyEqual(pt1
[1], pt2
[1])
1596 if self
._spaceAttachments
:
1597 if line
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
:
1598 # Align line according to the next handle along
1599 point
= line
.GetNextControlPoint(self
)
1600 if point
[0] < firstPoint
[0]:
1602 elif point
[0] > secondPoint
[0]:
1607 x
= firstPoint
[0] + (nth
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs
+ 1.0)
1609 x
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint
1612 assert RoughlyEqual(pt1
[0], pt2
[0])
1621 if self
._spaceAttachments
:
1622 if line
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
:
1623 # Align line according to the next handle along
1624 point
= line
.GetNextControlPoint(self
)
1625 if point
[1] < firstPoint
[1]:
1627 elif point
[1] > secondPoint
[1]:
1632 y
= firstPoint
[1] + (nth
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs
+ 1.0)
1634 y
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint
1639 # Return the zero-based position in m_lines of line
1640 def GetLinePosition(self
, line
):
1641 """Get the zero-based position of line in the list of lines
1645 return self
._lines
.index(line
)
1653 # shoulder1 ->---------<- shoulder2
1655 # <- branching attachment point N-1
1657 def GetBranchingAttachmentInfo(self
, attachment
):
1658 """Get information about where branching connections go.
1660 Returns FALSE if there are no lines at this attachment.
1662 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1664 # Number of lines at this attachment
1665 lineCount
= self
.GetAttachmentLineCount(attachment
)
1670 totalBranchLength
= self
._branchSpacing
* (lineCount
- 1)
1671 root
= self
.GetBranchingAttachmentRoot(attachment
)
1673 neck
= wx
.RealPoint()
1674 shoulder1
= wx
.RealPoint()
1675 shoulder2
= wx
.RealPoint()
1677 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1678 if physicalAttachment
== 0:
1679 neck
[0] = self
.GetX()
1680 neck
[1] = root
[1] - self
._branchNeckLength
1682 shoulder1
[0] = root
[0] - totalBranchLength
/ 2.0
1683 shoulder2
[0] = root
[0] + totalBranchLength
/ 2.0
1685 shoulder1
[1] = neck
[1]
1686 shoulder2
[1] = neck
[1]
1687 elif physicalAttachment
== 1:
1688 neck
[0] = root
[0] + self
._branchNeckLength
1691 shoulder1
[0] = neck
[0]
1692 shoulder2
[0] = neck
[0]
1694 shoulder1
[1] = neck
[1] - totalBranchLength
/ 2.0
1695 shoulder1
[1] = neck
[1] + totalBranchLength
/ 2.0
1696 elif physicalAttachment
== 2:
1697 neck
[0] = self
.GetX()
1698 neck
[1] = root
[1] + self
._branchNeckLength
1700 shoulder1
[0] = root
[0] - totalBranchLength
/ 2.0
1701 shoulder2
[0] = root
[0] + totalBranchLength
/ 2.0
1703 shoulder1
[1] = neck
[1]
1704 shoulder2
[1] = neck
[1]
1705 elif physicalAttachment
== 3:
1706 neck
[0] = root
[0] - self
._branchNeckLength
1709 shoulder1
[0] = neck
[0]
1710 shoulder2
[0] = neck
[0]
1712 shoulder1
[1] = neck
[1] - totalBranchLength
/ 2.0
1713 shoulder2
[1] = neck
[1] + totalBranchLength
/ 2.0
1715 raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
1716 return root
, neck
, shoulder1
, shoulder2
1718 def GetBranchingAttachmentPoint(self
, attachment
, n
):
1719 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1721 root
, neck
, shoulder1
, shoulder2
= self
.GetBranchingAttachmentInfo(attachment
)
1723 stemPt
= wx
.RealPoint()
1725 if physicalAttachment
== 0:
1726 pt
[1] = neck
[1] - self
._branchStemLength
1727 pt
[0] = shoulder1
[0] + n
* self
._branchSpacing
1731 elif physicalAttachment
== 2:
1732 pt
[1] = neck
[1] + self
._branchStemLength
1733 pt
[0] = shoulder1
[0] + n
* self
._branchStemLength
1737 elif physicalAttachment
== 1:
1738 pt
[0] = neck
[0] + self
._branchStemLength
1739 pt
[1] = shoulder1
[1] + n
* self
._branchSpacing
1743 elif physicalAttachment
== 3:
1744 pt
[0] = neck
[0] - self
._branchStemLength
1745 pt
[1] = shoulder1
[1] + n
* self
._branchSpacing
1750 raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
1754 def GetAttachmentLineCount(self
, attachment
):
1755 """Get the number of lines at this attachment position."""
1757 for lineShape
in self
._lines
:
1758 if lineShape
.GetFrom() == self
and lineShape
.GetAttachmentFrom() == attachment
:
1760 elif lineShape
.GetTo() == self
and lineShape
.GetAttachmentTo() == attachment
:
1764 def GetBranchingAttachmentRoot(self
, attachment
):
1765 """Get the root point at the given attachment."""
1766 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1768 root
= wx
.RealPoint()
1770 width
, height
= self
.GetBoundingBoxMax()
1772 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1773 if physicalAttachment
== 0:
1774 root
[0] = self
.GetX()
1775 root
[1] = self
.GetY() - height
/ 2.0
1776 elif physicalAttachment
== 1:
1777 root
[0] = self
.GetX() + width
/ 2.0
1778 root
[1] = self
.GetY()
1779 elif physicalAttachment
== 2:
1780 root
[0] = self
.GetX()
1781 root
[1] = self
.GetY() + height
/ 2.0
1782 elif physicalAttachment
== 3:
1783 root
[0] = self
.GetX() - width
/ 2.0
1784 root
[1] = self
.GetY()
1786 raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
1790 # Draw or erase the branches (not the actual arcs though)
1791 def OnDrawBranchesAttachment(self
, dc
, attachment
, erase
= False):
1792 count
= self
.GetAttachmentLineCount(attachment
)
1796 root
, neck
, shoulder1
, shoulder2
= self
.GetBranchingAttachmentInfo(attachment
)
1799 dc
.SetPen(wx
.WHITE_PEN
)
1800 dc
.SetBrush(wx
.WHITE_BRUSH
)
1802 dc
.SetPen(wx
.BLACK_PEN
)
1803 dc
.SetBrush(wx
.BLACK_BRUSH
)
1806 dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1])
1809 # Draw shoulder-to-shoulder line
1810 dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1])
1811 # Draw all the little branches
1812 for i
in range(count
):
1813 pt
, stemPt
= self
.GetBranchingAttachmentPoint(attachment
, i
)
1814 dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1])
1816 if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB
and count
> 1:
1818 dc
.DrawEllipse(stemPt
[0] - blobSize
/ 2.0, stemPt
[1] - blobSize
/ 2.0, blobSize
, blobSize
)
1820 def OnDrawBranches(self
, dc
, erase
= False):
1821 if self
._attachmentMode
!= ATTACHMENT_MODE_BRANCHING
:
1823 for i
in range(self
.GetNumberOfAttachments()):
1824 self
.OnDrawBranchesAttachment(dc
, i
, erase
)
1826 def GetAttachmentPositionEdge(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
1827 """ Only get the attachment position at the _edge_ of the shape,
1828 ignoring branching mode. This is used e.g. to indicate the edge of
1829 interest, not the point on the attachment branch.
1831 oldMode
= self
._attachmentMode
1833 # Calculate as if to edge, not branch
1834 if self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
1835 self
._attachmentMode
= ATTACHMENT_MODE_EDGE
1836 res
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
)
1837 self
._attachmentMode
= oldMode
1841 def PhysicalToLogicalAttachment(self
, physicalAttachment
):
1842 """ Rotate the standard attachment point from physical
1843 (0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
1845 if RoughlyEqual(self
.GetRotation(), 0):
1846 i
= physicalAttachment
1847 elif RoughlyEqual(self
.GetRotation(), math
.pi
/ 2.0):
1848 i
= physicalAttachment
- 1
1849 elif RoughlyEqual(self
.GetRotation(), math
.pi
):
1850 i
= physicalAttachment
- 2
1851 elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi
/ 2.0):
1852 i
= physicalAttachment
- 3
1854 # Can't handle -- assume the same
1855 return physicalAttachment
1862 def LogicalToPhysicalAttachment(self
, logicalAttachment
):
1863 """Rotate the standard attachment point from logical
1864 to physical (0 is always North).
1866 if RoughlyEqual(self
.GetRotation(), 0):
1867 i
= logicalAttachment
1868 elif RoughlyEqual(self
.GetRotation(), math
.pi
/ 2.0):
1869 i
= logicalAttachment
+ 1
1870 elif RoughlyEqual(self
.GetRotation(), math
.pi
):
1871 i
= logicalAttachment
+ 2
1872 elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi
/ 2.0):
1873 i
= logicalAttachment
+ 3
1875 return logicalAttachment
1882 def Rotate(self
, x
, y
, theta
):
1883 """Rotate about the given axis by the given amount in radians."""
1884 self
._rotation
= theta
1885 if self
._rotation
< 0:
1886 self
._rotation
+= 2 * math
.pi
1887 elif self
._rotation
> 2 * math
.pi
:
1888 self
._rotation
-= 2 * math
.pi
1890 def GetBackgroundPen(self
):
1891 """Return pen of the right colour for the background."""
1892 if self
.GetCanvas():
1893 return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
)
1894 return WhiteBackgroundPen
1896 def GetBackgroundBrush(self
):
1897 """Return brush of the right colour for the background."""
1898 if self
.GetCanvas():
1899 return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
)
1900 return WhiteBackgroundBrush
1903 """Get the x position of the centre of the shape."""
1907 """Get the y position of the centre of the shape."""
1911 """Set the x position of the shape."""
1915 """Set the y position of the shape."""
1918 def GetParent(self
):
1919 """Return the parent of this shape, if it is part of a composite."""
1922 def SetParent(self
, p
):
1925 def GetChildren(self
):
1926 """Return the list of children for this shape."""
1927 return self
._children
1929 def GetDrawHandles(self
):
1930 """Return the list of drawhandles."""
1931 return self
._drawHandles
1933 def GetEventHandler(self
):
1934 """Return the event handler for this shape."""
1935 return self
._eventHandler
1937 def SetEventHandler(self
, handler
):
1938 """Set the event handler for this shape."""
1939 self
._eventHandler
= handler
1941 def Recompute(self
):
1942 """Recomputes any constraints associated with the shape.
1944 Normally applicable to CompositeShapes only, but harmless for
1945 other classes of Shape.
1949 def IsHighlighted(self
):
1950 """TRUE if the shape is highlighted. Shape highlighting is unimplemented."""
1951 return self
._highlighted
1953 def GetSensitivityFilter(self
):
1954 """Return the sensitivity filter, a bitlist of values.
1956 See Shape.SetSensitivityFilter.
1958 return self
._sensitivity
1960 def SetFixedSize(self
, x
, y
):
1961 """Set the shape to be fixed size."""
1962 self
._fixedWidth
= x
1963 self
._fixedHeight
= y
1965 def GetFixedSize(self
):
1966 """Return flags indicating whether the shape is of fixed size in
1969 return self
._fixedWidth
, self
._fixedHeight
1971 def GetFixedWidth(self
):
1972 """TRUE if the shape cannot be resized in the horizontal plane."""
1973 return self
._fixedWidth
1975 def GetFixedHeight(self
):
1976 """TRUE if the shape cannot be resized in the vertical plane."""
1977 return self
._fixedHeight
1979 def SetSpaceAttachments(self
, sp
):
1980 """Indicate whether lines should be spaced out evenly at the point
1981 they touch the node (sp = True), or whether they should join at a single
1984 self
._spaceAttachments
= sp
1986 def GetSpaceAttachments(self
):
1987 """Return whether lines should be spaced out evenly at the point they
1988 touch the node (True), or whether they should join at a single point
1991 return self
._spaceAttachments
1993 def SetCentreResize(self
, cr
):
1994 """Specify whether the shape is to be resized from the centre (the
1995 centre stands still) or from the corner or side being dragged (the
1996 other corner or side stands still).
1998 self
._centreResize
= cr
2000 def GetCentreResize(self
):
2001 """TRUE if the shape is to be resized from the centre (the centre stands
2002 still), or FALSE if from the corner or side being dragged (the other
2003 corner or side stands still)
2005 return self
._centreResize
2007 def SetMaintainAspectRatio(self
, ar
):
2008 """Set whether a shape that resizes should not change the aspect ratio
2009 (width and height should be in the original proportion).
2011 self
._maintainAspectRatio
= ar
2013 def GetMaintainAspectRatio(self
):
2014 """TRUE if shape keeps aspect ratio during resize."""
2015 return self
._maintainAspectRatio
2018 """Return the list of lines connected to this shape."""
2021 def SetDisableLabel(self
, flag
):
2022 """Set flag to TRUE to stop the default region being shown."""
2023 self
._disableLabel
= flag
2025 def GetDisableLabel(self
):
2026 """TRUE if the default region will not be shown, FALSE otherwise."""
2027 return self
._disableLabel
2029 def SetAttachmentMode(self
, mode
):
2030 """Set the attachment mode.
2032 If TRUE, attachment points will be significant when drawing lines to
2033 and from this shape.
2034 If FALSE, lines will be drawn as if to the centre of the shape.
2036 self
._attachmentMode
= mode
2038 def GetAttachmentMode(self
):
2039 """Return the attachment mode.
2041 See Shape.SetAttachmentMode.
2043 return self
._attachmentMode
2046 """Set the integer identifier for this shape."""
2050 """Return the integer identifier for this shape."""
2054 """TRUE if the shape is in a visible state, FALSE otherwise.
2056 Note that this has nothing to do with whether the window is hidden
2057 or the shape has scrolled off the canvas; it refers to the internal
2060 return self
._visible
2063 """Return the pen used for drawing the shape's outline."""
2067 """Return the brush used for filling the shape."""
2070 def GetNumberOfTextRegions(self
):
2071 """Return the number of text regions for this shape."""
2072 return len(self
._regions
)
2074 def GetRegions(self
):
2075 """Return the list of ShapeRegions."""
2076 return self
._regions
2078 # Control points ('handles') redirect control to the actual shape, to
2079 # make it easier to override sizing behaviour.
2080 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
2081 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2083 dc
= wx
.ClientDC(self
.GetCanvas())
2084 self
.GetCanvas().PrepareDC(dc
)
2086 dc
.SetLogicalFunction(OGLRBLF
)
2088 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2089 dc
.SetPen(dottedPen
)
2090 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2092 if self
.GetCentreResize():
2093 # Maintain the same centre point
2094 new_width
= 2.0 * abs(x
- self
.GetX())
2095 new_height
= 2.0 * abs(y
- self
.GetY())
2097 # Constrain sizing according to what control point you're dragging
2098 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2099 if self
.GetMaintainAspectRatio():
2100 new_height
= bound_y
* (new_width
/ bound_x
)
2102 new_height
= bound_y
2103 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2104 if self
.GetMaintainAspectRatio():
2105 new_width
= bound_x
* (new_height
/ bound_y
)
2108 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
):
2109 new_height
= bound_y
* (new_width
/ bound_x
)
2111 if self
.GetFixedWidth():
2114 if self
.GetFixedHeight():
2115 new_height
= bound_y
2117 pt
._controlPointDragEndWidth
= new_width
2118 pt
._controlPointDragEndHeight
= new_height
2120 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
)
2122 # Don't maintain the same centre point
2123 newX1
= min(pt
._controlPointDragStartX
, x
)
2124 newY1
= min(pt
._controlPointDragStartY
, y
)
2125 newX2
= max(pt
._controlPointDragStartX
, x
)
2126 newY2
= max(pt
._controlPointDragStartY
, y
)
2127 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2128 newY1
= pt
._controlPointDragStartY
2129 newY2
= newY1
+ pt
._controlPointDragStartHeight
2130 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2131 newX1
= pt
._controlPointDragStartX
2132 newX2
= newX1
+ pt
._controlPointDragStartWidth
2133 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
or self
.GetMaintainAspectRatio()):
2134 newH
= (newX2
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
)
2135 if self
.GetY() > pt
._controlPointDragStartY
:
2136 newY2
= newY1
+ newH
2138 newY1
= newY2
- newH
2140 newWidth
= float(newX2
- newX1
)
2141 newHeight
= float(newY2
- newY1
)
2143 if pt
._type
== CONTROL_POINT_VERTICAL
and self
.GetMaintainAspectRatio():
2144 newWidth
= bound_x
* (newHeight
/ bound_y
)
2146 if pt
._type
== CONTROL_POINT_HORIZONTAL
and self
.GetMaintainAspectRatio():
2147 newHeight
= bound_y
* (newWidth
/ bound_x
)
2149 pt
._controlPointDragPosX
= newX1
+ newWidth
/ 2.0
2150 pt
._controlPointDragPosY
= newY1
+ newHeight
/ 2.0
2151 if self
.GetFixedWidth():
2154 if self
.GetFixedHeight():
2157 pt
._controlPointDragEndWidth
= newWidth
2158 pt
._controlPointDragEndHeight
= newHeight
2159 self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
)
2161 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2162 self
._canvas
.CaptureMouse()
2164 dc
= wx
.ClientDC(self
.GetCanvas())
2165 self
.GetCanvas().PrepareDC(dc
)
2167 dc
.SetLogicalFunction(OGLRBLF
)
2169 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2170 self
.GetEventHandler().OnEndSize(bound_x
, bound_y
)
2172 # Choose the 'opposite corner' of the object as the stationary
2173 # point in case this is non-centring resizing.
2174 if pt
.GetX() < self
.GetX():
2175 pt
._controlPointDragStartX
= self
.GetX() + bound_x
/ 2.0
2177 pt
._controlPointDragStartX
= self
.GetX() - bound_x
/ 2.0
2179 if pt
.GetY() < self
.GetY():
2180 pt
._controlPointDragStartY
= self
.GetY() + bound_y
/ 2.0
2182 pt
._controlPointDragStartY
= self
.GetY() - bound_y
/ 2.0
2184 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2185 pt
._controlPointDragStartY
= self
.GetY() - bound_y
/ 2.0
2186 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2187 pt
._controlPointDragStartX
= self
.GetX() - bound_x
/ 2.0
2189 # We may require the old width and height
2190 pt
._controlPointDragStartWidth
= bound_x
2191 pt
._controlPointDragStartHeight
= bound_y
2193 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2194 dc
.SetPen(dottedPen
)
2195 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2197 if self
.GetCentreResize():
2198 new_width
= 2.0 * abs(x
- self
.GetX())
2199 new_height
= 2.0 * abs(y
- self
.GetY())
2201 # Constrain sizing according to what control point you're dragging
2202 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2203 if self
.GetMaintainAspectRatio():
2204 new_height
= bound_y
* (new_width
/ bound_x
)
2206 new_height
= bound_y
2207 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2208 if self
.GetMaintainAspectRatio():
2209 new_width
= bound_x
* (new_height
/ bound_y
)
2212 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
):
2213 new_height
= bound_y
* (new_width
/ bound_x
)
2215 if self
.GetFixedWidth():
2218 if self
.GetFixedHeight():
2219 new_height
= bound_y
2221 pt
._controlPointDragEndWidth
= new_width
2222 pt
._controlPointDragEndHeight
= new_height
2223 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
)
2225 # Don't maintain the same centre point
2226 newX1
= min(pt
._controlPointDragStartX
, x
)
2227 newY1
= min(pt
._controlPointDragStartY
, y
)
2228 newX2
= max(pt
._controlPointDragStartX
, x
)
2229 newY2
= max(pt
._controlPointDragStartY
, y
)
2230 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2231 newY1
= pt
._controlPointDragStartY
2232 newY2
= newY1
+ pt
._controlPointDragStartHeight
2233 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2234 newX1
= pt
._controlPointDragStartX
2235 newX2
= newX1
+ pt
._controlPointDragStartWidth
2236 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
or self
.GetMaintainAspectRatio()):
2237 newH
= (newX2
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
)
2238 if pt
.GetY() > pt
._controlPointDragStartY
:
2239 newY2
= newY1
+ newH
2241 newY1
= newY2
- newH
2243 newWidth
= float(newX2
- newX1
)
2244 newHeight
= float(newY2
- newY1
)
2246 if pt
._type
== CONTROL_POINT_VERTICAL
and self
.GetMaintainAspectRatio():
2247 newWidth
= bound_x
* (newHeight
/ bound_y
)
2249 if pt
._type
== CONTROL_POINT_HORIZONTAL
and self
.GetMaintainAspectRatio():
2250 newHeight
= bound_y
* (newWidth
/ bound_x
)
2252 pt
._controlPointDragPosX
= newX1
+ newWidth
/ 2.0
2253 pt
._controlPointDragPosY
= newY1
+ newHeight
/ 2.0
2254 if self
.GetFixedWidth():
2257 if self
.GetFixedHeight():
2260 pt
._controlPointDragEndWidth
= newWidth
2261 pt
._controlPointDragEndHeight
= newHeight
2262 self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
)
2264 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2265 dc
= wx
.ClientDC(self
.GetCanvas())
2266 self
.GetCanvas().PrepareDC(dc
)
2268 if self
._canvas
.HasCapture():
2269 self
._canvas
.ReleaseMouse()
2270 dc
.SetLogicalFunction(wx
.COPY
)
2272 self
.ResetControlPoints()
2276 self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
)
2278 # The next operation could destroy this control point (it does for
2279 # label objects, via formatting the text), so save all values we're
2280 # going to use, or we'll be accessing garbage.
2284 if self
.GetCentreResize():
2285 self
.Move(dc
, self
.GetX(), self
.GetY())
2287 self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
)
2289 # Recursively redraw links if we have a composite
2290 if len(self
.GetChildren()):
2291 self
.DrawLinks(dc
, -1, True)
2293 width
, height
= self
.GetBoundingBoxMax()
2294 self
.GetEventHandler().OnEndSize(width
, height
)
2296 if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
:
2297 self
._canvas
.Redraw(dc
)
2301 class RectangleShape(Shape
):
2303 The wxRectangleShape has rounded or square corners.
2308 def __init__(self
, w
= 0.0, h
= 0.0):
2309 Shape
.__init
__(self
)
2312 self
._cornerRadius
= 0.0
2313 self
.SetDefaultRegionSize()
2315 def OnDraw(self
, dc
):
2316 x1
= self
._xpos
- self
._width
/ 2.0
2317 y1
= self
._ypos
- self
._height
/ 2.0
2319 if self
._shadowMode
!= SHADOW_NONE
:
2320 if self
._shadowBrush
:
2321 dc
.SetBrush(self
._shadowBrush
)
2322 dc
.SetPen(TransparentPen
)
2324 if self
._cornerRadius
:
2325 dc
.DrawRoundedRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
)
2327 dc
.DrawRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
)
2330 if self
._pen
.GetWidth() == 0:
2331 dc
.SetPen(TransparentPen
)
2333 dc
.SetPen(self
._pen
)
2335 dc
.SetBrush(self
._brush
)
2337 if self
._cornerRadius
:
2338 dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
)
2340 dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
)
2342 def GetBoundingBoxMin(self
):
2343 return self
._width
, self
._height
2345 def SetSize(self
, x
, y
, recursive
= False):
2346 self
.SetAttachmentSize(x
, y
)
2347 self
._width
= max(x
, 1)
2348 self
._height
= max(y
, 1)
2349 self
.SetDefaultRegionSize()
2351 def GetCornerRadius(self
):
2352 """Get the radius of the rectangle's rounded corners."""
2353 return self
._cornerRadius
2355 def SetCornerRadius(self
, rad
):
2356 """Set the radius of the rectangle's rounded corners.
2358 If the radius is zero, a non-rounded rectangle will be drawn.
2359 If the radius is negative, the value is the proportion of the smaller
2360 dimension of the rectangle.
2362 self
._cornerRadius
= rad
2364 # Assume (x1, y1) is centre of box (most generally, line end at box)
2365 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2366 bound_x
, bound_y
= self
.GetBoundingBoxMax()
2367 return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
)
2372 def GetHeight(self
):
2375 def SetWidth(self
, w
):
2378 def SetHeight(self
, h
):
2383 class PolygonShape(Shape
):
2384 """A PolygonShape's shape is defined by a number of points passed to
2385 the object's constructor. It can be used to create new shapes such as
2386 diamonds and triangles.
2389 Shape
.__init
__(self
)
2392 self
._originalPoints
= None
2394 def Create(self
, the_points
= None):
2395 """Takes a list of wx.RealPoints or tuples; each point is an offset
2401 self
._originalPoints
= []
2404 self
._originalPoints
= the_points
2406 # Duplicate the list of points
2408 for point
in the_points
:
2409 new_point
= wx
.Point(point
[0], point
[1])
2410 self
._points
.append(new_point
)
2411 self
.CalculateBoundingBox()
2412 self
._originalWidth
= self
._boundWidth
2413 self
._originalHeight
= self
._boundHeight
2414 self
.SetDefaultRegionSize()
2416 def ClearPoints(self
):
2418 self
._originalPoints
= []
2420 # Width and height. Centre of object is centre of box
2421 def GetBoundingBoxMin(self
):
2422 return self
._boundWidth
, self
._boundHeight
2424 def GetPoints(self
):
2425 """Return the internal list of polygon vertices."""
2428 def GetOriginalPoints(self
):
2429 return self
._originalPoints
2431 def GetOriginalWidth(self
):
2432 return self
._originalWidth
2434 def GetOriginalHeight(self
):
2435 return self
._originalHeight
2437 def SetOriginalWidth(self
, w
):
2438 self
._originalWidth
= w
2440 def SetOriginalHeight(self
, h
):
2441 self
._originalHeight
= h
2443 def CalculateBoundingBox(self
):
2444 # Calculate bounding box at construction (and presumably resize) time
2450 for point
in self
._points
:
2453 if point
[0] > right
:
2458 if point
[1] > bottom
:
2461 self
._boundWidth
= right
- left
2462 self
._boundHeight
= bottom
- top
2464 def CalculatePolygonCentre(self
):
2465 """Recalculates the centre of the polygon, and
2466 readjusts the point offsets accordingly.
2467 Necessary since the centre of the polygon
2468 is expected to be the real centre of the bounding
2476 for point
in self
._points
:
2479 if point
[0] > right
:
2484 if point
[1] > bottom
:
2487 bwidth
= right
- left
2488 bheight
= bottom
- top
2490 newCentreX
= left
+ bwidth
/ 2.0
2491 newCentreY
= top
+ bheight
/ 2.0
2493 for i
in range(len(self
._points
)):
2494 self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
2495 self
._xpos
+= newCentreX
2496 self
._ypos
+= newCentreY
2498 def HitTest(self
, x
, y
):
2499 # Imagine four lines radiating from this point. If all of these lines
2500 # hit the polygon, we're inside it, otherwise we're not. Obviously
2501 # we'd need more radiating lines to be sure of correct results for
2502 # very strange (concave) shapes.
2503 endPointsX
= [x
, x
+ 1000, x
, x
- 1000]
2504 endPointsY
= [y
- 1000, y
, y
+ 1000, y
]
2509 for point
in self
._points
:
2510 xpoints
.append(point
[0] + self
._xpos
)
2511 ypoints
.append(point
[1] + self
._ypos
)
2513 # We assume it's inside the polygon UNLESS one or more
2514 # lines don't hit the outline.
2518 if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]):
2524 nearest_attachment
= 0
2526 # If a hit, check the attachment points within the object
2529 for i
in range(self
.GetNumberOfAttachments()):
2530 e
= self
.GetAttachmentPositionEdge(i
)
2533 l
= math
.sqrt((xp
- x
) * (xp
- x
) + (yp
- y
) * (yp
- y
))
2536 nearest_attachment
= i
2538 return nearest_attachment
, nearest
2540 # Really need to be able to reset the shape! Otherwise, if the
2541 # points ever go to zero, we've lost it, and can't resize.
2542 def SetSize(self
, new_width
, new_height
, recursive
= True):
2543 self
.SetAttachmentSize(new_width
, new_height
)
2545 # Multiply all points by proportion of new size to old size
2546 x_proportion
= abs(float(new_width
) / self
._originalWidth
)
2547 y_proportion
= abs(float(new_height
) / self
._originalHeight
)
2549 for i
in range(max(len(self
._points
), len(self
._originalPoints
))):
2550 self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
)
2552 self
._boundWidth
= abs(new_width
)
2553 self
._boundHeight
= abs(new_height
)
2554 self
.SetDefaultRegionSize()
2556 # Make the original points the same as the working points
2557 def UpdateOriginalPoints(self
):
2558 """If we've changed the shape, must make the original points match the
2559 working points with this function.
2561 self
._originalPoints
= []
2563 for point
in self
._points
:
2564 original_point
= wx
.RealPoint(point
[0], point
[1])
2565 self
._originalPoints
.append(original_point
)
2567 self
.CalculateBoundingBox()
2568 self
._originalWidth
= self
._boundWidth
2569 self
._originalHeight
= self
._boundHeight
2571 def AddPolygonPoint(self
, pos
):
2572 """Add a control point after the given point."""
2574 firstPoint
= self
._points
[pos
]
2576 firstPoint
= self
._points
[0]
2579 secondPoint
= self
._points
[pos
+ 1]
2581 secondPoint
= self
._points
[0]
2583 x
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0]
2584 y
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1]
2585 point
= wx
.RealPoint(x
, y
)
2587 if pos
>= len(self
._points
) - 1:
2588 self
._points
.append(point
)
2590 self
._points
.insert(pos
+ 1, point
)
2592 self
.UpdateOriginalPoints()
2595 self
.DeleteControlPoints()
2596 self
.MakeControlPoints()
2598 def DeletePolygonPoint(self
, pos
):
2599 """Delete the given control point."""
2600 if pos
< len(self
._points
):
2601 del self
._points
[pos
]
2602 self
.UpdateOriginalPoints()
2604 self
.DeleteControlPoints()
2605 self
.MakeControlPoints()
2607 # Assume (x1, y1) is centre of box (most generally, line end at box)
2608 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2609 # First check for situation where the line is vertical,
2610 # and we would want to connect to a point on that vertical --
2611 # oglFindEndForPolyline can't cope with this (the arrow
2612 # gets drawn to the wrong place).
2613 if self
._attachmentMode
== ATTACHMENT_MODE_NONE
and x1
== x2
:
2614 # Look for the point we'd be connecting to. This is
2616 for point
in self
._points
:
2618 if y2
> y1
and point
[1] > 0:
2619 return point
[0] + self
._xpos
, point
[1] + self
._ypos
2620 elif y2
< y1
and point
[1] < 0:
2621 return point
[0] + self
._xpos
, point
[1] + self
._ypos
2625 for point
in self
._points
:
2626 xpoints
.append(point
[0] + self
._xpos
)
2627 ypoints
.append(point
[1] + self
._ypos
)
2629 return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
)
2631 def OnDraw(self
, dc
):
2632 if self
._shadowMode
!= SHADOW_NONE
:
2633 if self
._shadowBrush
:
2634 dc
.SetBrush(self
._shadowBrush
)
2635 dc
.SetPen(TransparentPen
)
2637 dc
.DrawPolygon(self
._points
, self
._xpos
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
)
2640 if self
._pen
.GetWidth() == 0:
2641 dc
.SetPen(TransparentPen
)
2643 dc
.SetPen(self
._pen
)
2645 dc
.SetBrush(self
._brush
)
2646 dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
)
2648 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
2649 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2650 # Multiply all points by proportion of new size to old size
2651 x_proportion
= abs(float(w
) / self
._originalWidth
)
2652 y_proportion
= abs(float(h
) / self
._originalHeight
)
2655 for point
in self
._originalPoints
:
2656 intPoints
.append(wx
.Point(x_proportion
* point
[0], y_proportion
* point
[1]))
2657 dc
.DrawPolygon(intPoints
, x
, y
)
2659 # Make as many control points as there are vertices
2660 def MakeControlPoints(self
):
2661 for point
in self
._points
:
2662 control
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1])
2663 self
._canvas
.AddShape(control
)
2664 self
._controlPoints
.append(control
)
2666 def ResetControlPoints(self
):
2667 for i
in range(min(len(self
._points
), len(self
._controlPoints
))):
2668 point
= self
._points
[i
]
2669 self
._controlPoints
[i
]._xoffset
= point
[0]
2670 self
._controlPoints
[i
]._yoffset
= point
[1]
2671 self
._controlPoints
[i
].polygonVertex
= point
2673 def GetNumberOfAttachments(self
):
2674 maxN
= max(len(self
._points
) - 1, 0)
2675 for point
in self
._attachmentPoints
:
2676 if point
._id
> maxN
:
2680 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
2681 if self
._attachmentMode
== ATTACHMENT_MODE_EDGE
and self
._points
and attachment
< len(self
._points
):
2682 point
= self
._points
[0]
2683 return point
[0] + self
._xpos
, point
[1] + self
._ypos
2684 return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
)
2686 def AttachmentIsValid(self
, attachment
):
2687 if not self
._points
:
2690 if attachment
>= 0 and attachment
< len(self
._points
):
2693 for point
in self
._attachmentPoints
:
2694 if point
._id
== attachment
:
2699 # Rotate about the given axis by the given amount in radians
2700 def Rotate(self
, x
, y
, theta
):
2701 actualTheta
= theta
- self
._rotation
2703 # Rotate attachment points
2704 sinTheta
= math
.sin(actualTheta
)
2705 cosTheta
= math
.cos(actualTheta
)
2707 for point
in self
._attachmentPoints
:
2711 point
._x
= x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
2712 point
._y
= x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2714 for i
in range(len(self
._points
)):
2715 x1
, y1
= self
._points
[i
]
2717 self
._points
[i
] = x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
, x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2719 for i
in range(len(self
._originalPoints
)):
2720 x1
, y1
= self
._originalPoints
[i
]
2722 self
._originalPoints
[i
] = x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
, x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2724 # Added by Pierre Hjälm. If we don't do this the outline will be
2725 # the wrong size. Hopefully it won't have any ill effects.
2726 self
.UpdateOriginalPoints()
2728 self
._rotation
= theta
2730 self
.CalculatePolygonCentre()
2731 self
.CalculateBoundingBox()
2732 self
.ResetControlPoints()
2734 # Control points ('handles') redirect control to the actual shape, to
2735 # make it easier to override sizing behaviour.
2736 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
2737 dc
= wx
.ClientDC(self
.GetCanvas())
2738 self
.GetCanvas().PrepareDC(dc
)
2740 dc
.SetLogicalFunction(OGLRBLF
)
2742 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2743 dc
.SetPen(dottedPen
)
2744 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2746 # Code for CTRL-drag in C++ version commented out
2748 pt
.CalculateNewSize(x
, y
)
2750 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1])
2752 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2753 dc
= wx
.ClientDC(self
.GetCanvas())
2754 self
.GetCanvas().PrepareDC(dc
)
2758 dc
.SetLogicalFunction(OGLRBLF
)
2760 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2762 dist
= math
.sqrt((x
- self
.GetX()) * (x
- self
.GetX()) + (y
- self
.GetY()) * (y
- self
.GetY()))
2764 pt
._originalDistance
= dist
2765 pt
._originalSize
[0] = bound_x
2766 pt
._originalSize
[1] = bound_y
2768 if pt
._originalDistance
== 0:
2769 pt
._originalDistance
= 0.0001
2771 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2772 dc
.SetPen(dottedPen
)
2773 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2775 # Code for CTRL-drag in C++ version commented out
2777 pt
.CalculateNewSize(x
, y
)
2779 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1])
2781 self
._canvas
.CaptureMouse()
2783 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2784 dc
= wx
.ClientDC(self
.GetCanvas())
2785 self
.GetCanvas().PrepareDC(dc
)
2787 if self
._canvas
.HasCapture():
2788 self
._canvas
.ReleaseMouse()
2789 dc
.SetLogicalFunction(wx
.COPY
)
2791 # If we're changing shape, must reset the original points
2793 self
.CalculateBoundingBox()
2794 self
.CalculatePolygonCentre()
2796 self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1])
2799 self
.ResetControlPoints()
2800 self
.Move(dc
, self
.GetX(), self
.GetY())
2801 if not self
._canvas
.GetQuickEditMode():
2802 self
._canvas
.Redraw(dc
)
2806 class EllipseShape(Shape
):
2807 """The EllipseShape behaves similarly to the RectangleShape but is
2813 def __init__(self
, w
, h
):
2814 Shape
.__init
__(self
)
2817 self
.SetDefaultRegionSize()
2819 def GetBoundingBoxMin(self
):
2820 return self
._width
, self
._height
2822 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2823 bound_x
, bound_y
= self
.GetBoundingBoxMax()
2825 return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
)
2830 def GetHeight(self
):
2833 def SetWidth(self
, w
):
2836 def SetHeight(self
, h
):
2839 def OnDraw(self
, dc
):
2840 if self
._shadowMode
!= SHADOW_NONE
:
2841 if self
._shadowBrush
:
2842 dc
.SetBrush(self
._shadowBrush
)
2843 dc
.SetPen(TransparentPen
)
2844 dc
.DrawEllipse(self
._xpos
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
,
2845 self
._ypos
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
,
2846 self
.GetWidth(), self
.GetHeight())
2849 if self
._pen
.GetWidth() == 0:
2850 dc
.SetPen(TransparentPen
)
2852 dc
.SetPen(self
._pen
)
2854 dc
.SetBrush(self
._brush
)
2855 dc
.DrawEllipse(self
._xpos
- self
.GetWidth() / 2.0, self
._ypos
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight())
2857 def SetSize(self
, x
, y
, recursive
= True):
2858 self
.SetAttachmentSize(x
, y
)
2861 self
.SetDefaultRegionSize()
2863 def GetNumberOfAttachments(self
):
2864 return Shape
.GetNumberOfAttachments(self
)
2866 # There are 4 attachment points on an ellipse - 0 = top, 1 = right,
2867 # 2 = bottom, 3 = left.
2868 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
2869 if self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
2870 return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
)
2872 if self
._attachmentMode
!= ATTACHMENT_MODE_NONE
:
2873 top
= self
._ypos
+ self
._height
/ 2.0
2874 bottom
= self
._ypos
- self
._height
/ 2.0
2875 left
= self
._xpos
- self
._width
/ 2.0
2876 right
= self
._xpos
+ self
._width
/ 2.0
2878 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
2880 if physicalAttachment
== 0:
2881 if self
._spaceAttachments
:
2882 x
= left
+ (nth
+ 1) * self
._width
/ (no_arcs
+ 1.0)
2886 # We now have the point on the bounding box: but get the point
2887 # on the ellipse by imagining a vertical line from
2888 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting
2891 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos
- self
._height
- 500, x
, self
._ypos
)
2892 elif physicalAttachment
== 1:
2894 if self
._spaceAttachments
:
2895 y
= bottom
+ (nth
+ 1) * self
._height
/ (no_arcs
+ 1.0)
2898 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos
+ self
._width
+ 500, y
, self
._xpos
, y
)
2899 elif physicalAttachment
== 2:
2900 if self
._spaceAttachments
:
2901 x
= left
+ (nth
+ 1) * self
._width
/ (no_arcs
+ 1.0)
2905 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos
+ self
._height
+ 500, x
, self
._ypos
)
2906 elif physicalAttachment
== 3:
2908 if self
._spaceAttachments
:
2909 y
= bottom
+ (nth
+ 1) * self
._height
/ (no_arcs
+ 1.0)
2912 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos
- self
._width
- 500, y
, self
._xpos
, y
)
2914 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
)
2916 return self
._xpos
, self
._ypos
2920 class CircleShape(EllipseShape
):
2921 """An EllipseShape whose width and height are the same."""
2922 def __init__(self
, diameter
):
2923 EllipseShape
.__init
__(self
, diameter
, diameter
)
2924 self
.SetMaintainAspectRatio(True)
2926 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2927 return FindEndForCircle(self
._width
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
)
2931 class TextShape(RectangleShape
):
2932 """As wxRectangleShape, but only the text is displayed."""
2933 def __init__(self
, width
, height
):
2934 RectangleShape
.__init
__(self
, width
, height
)
2936 def OnDraw(self
, dc
):
2941 class ShapeRegion(object):
2942 """Object region."""
2943 def __init__(self
, region
= None):
2945 self
._regionText
= region
._regionText
2946 self
._regionName
= region
._regionName
2947 self
._textColour
= region
._textColour
2949 self
._font
= region
._font
2950 self
._minHeight
= region
._minHeight
2951 self
._minWidth
= region
._minWidth
2952 self
._width
= region
._width
2953 self
._height
= region
._height
2957 self
._regionProportionX
= region
._regionProportionX
2958 self
._regionProportionY
= region
._regionProportionY
2959 self
._formatMode
= region
._formatMode
2960 self
._actualColourObject
= region
._actualColourObject
2961 self
._actualPenObject
= None
2962 self
._penStyle
= region
._penStyle
2963 self
._penColour
= region
._penColour
2966 for line
in region
._formattedText
:
2967 new_line
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText())
2968 self
._formattedText
.append(new_line
)
2970 self
._regionText
= ""
2971 self
._font
= NormalFont
2972 self
._minHeight
= 5.0
2973 self
._minWidth
= 5.0
2979 self
._regionProportionX
= -1.0
2980 self
._regionProportionY
= -1.0
2981 self
._formatMode
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
2982 self
._regionName
= ""
2983 self
._textColour
= "BLACK"
2984 self
._penColour
= "BLACK"
2985 self
._penStyle
= wx
.SOLID
2986 self
._actualColourObject
= wx
.TheColourDatabase
.Find("BLACK")
2987 self
._actualPenObject
= None
2989 self
._formattedText
= []
2991 def ClearText(self
):
2992 self
._formattedText
= []
2994 def SetFont(self
, f
):
2997 def SetMinSize(self
, w
, h
):
3001 def SetSize(self
, w
, h
):
3005 def SetPosition(self
, xp
, yp
):
3009 def SetProportions(self
, xp
, yp
):
3010 self
._regionProportionX
= xp
3011 self
._regionProportionY
= yp
3013 def SetFormatMode(self
, mode
):
3014 self
._formatMode
= mode
3016 def SetColour(self
, col
):
3017 self
._textColour
= col
3018 self
._actualColourObject
= col
3020 def GetActualColourObject(self
):
3021 self
._actualColourObject
= wx
.TheColourDatabase
.Find(self
.GetColour())
3022 return self
._actualColourObject
3024 def SetPenColour(self
, col
):
3025 self
._penColour
= col
3026 self
._actualPenObject
= None
3028 # Returns NULL if the pen is invisible
3029 # (different to pen being transparent; indicates that
3030 # region boundary should not be drawn.)
3031 def GetActualPen(self
):
3032 if self
._actualPenObject
:
3033 return self
._actualPenObject
3035 if not self
._penColour
:
3037 if self
._penColour
=="Invisible":
3039 self
._actualPenObject
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
)
3040 return self
._actualPenObject
3042 def SetText(self
, s
):
3043 self
._regionText
= s
3045 def SetName(self
, s
):
3046 self
._regionName
= s
3049 return self
._regionText
3054 def GetMinSize(self
):
3055 return self
._minWidth
, self
._minHeight
3057 def GetProportion(self
):
3058 return self
._regionProportionX
, self
._regionProportionY
3061 return self
._width
, self
._height
3063 def GetPosition(self
):
3064 return self
._x
, self
._y
3066 def GetFormatMode(self
):
3067 return self
._formatMode
3070 return self
._regionName
3072 def GetColour(self
):
3073 return self
._textColour
3075 def GetFormattedText(self
):
3076 return self
._formattedText
3078 def GetPenColour(self
):
3079 return self
._penColour
3081 def GetPenStyle(self
):
3082 return self
._penStyle
3084 def SetPenStyle(self
, style
):
3085 self
._penStyle
= style
3086 self
._actualPenObject
= None
3091 def GetHeight(self
):
3096 class ControlPoint(RectangleShape
):
3097 def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
):
3098 RectangleShape
.__init
__(self
, size
, size
)
3100 self
._canvas
= theCanvas
3101 self
._shape
= object
3102 self
._xoffset
= the_xoffset
3103 self
._yoffset
= the_yoffset
3104 self
._type
= the_type
3105 self
.SetPen(BlackForegroundPen
)
3106 self
.SetBrush(wx
.BLACK_BRUSH
)
3107 self
._oldCursor
= None
3108 self
._visible
= True
3109 self
._eraseObject
= True
3111 # Don't even attempt to draw any text - waste of time
3112 def OnDrawContents(self
, dc
):
3115 def OnDraw(self
, dc
):
3116 self
._xpos
= self
._shape
.GetX() + self
._xoffset
3117 self
._ypos
= self
._shape
.GetY() + self
._yoffset
3118 RectangleShape
.OnDraw(self
, dc
)
3120 def OnErase(self
, dc
):
3121 RectangleShape
.OnErase(self
, dc
)
3123 # Implement resizing of canvas object
3124 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
3125 self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
)
3127 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3128 self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
)
3130 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3131 self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
)
3133 def GetNumberOfAttachments(self
):
3136 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
3137 return self
._xpos
, self
._ypos
3139 def SetEraseObject(self
, er
):
3140 self
._eraseObject
= er
3143 class PolygonControlPoint(ControlPoint
):
3144 def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
):
3145 ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0)
3146 self
._polygonVertex
= vertex
3147 self
._originalDistance
= 0.0
3148 self
._newSize
= wx
.RealPoint()
3149 self
._originalSize
= wx
.RealPoint()
3151 def GetNewSize(self
):
3152 return self
._newSize
3154 # Calculate what new size would be, at end of resize
3155 def CalculateNewSize(self
, x
, y
):
3156 bound_x
, bound_y
= self
.GetShape().GetBoundingBoxMax()
3157 dist
= math
.sqrt((x
- self
._shape
.GetX()) * (x
- self
._shape
.GetX()) + (y
- self
._shape
.GetY()) * (y
- self
._shape
.GetY()))
3159 self
._newSize
[0] = dist
/ self
._originalDistance
* self
._originalSize
[0]
3160 self
._newSize
[1] = dist
/ self
._originalDistance
* self
._originalSize
[1]
3162 # Implement resizing polygon or moving the vertex
3163 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
3164 self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
)
3166 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3167 self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
)
3169 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3170 self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
)
3172 from _canvas
import *
3173 from _lines
import *
3174 from _composit
import *