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