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 *
24 global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
25 global BlackForegroundPen
, NormalFont
27 WhiteBackgroundPen
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)
28 WhiteBackgroundBrush
= wx
.Brush(wx
.WHITE
, wx
.SOLID
)
30 TransparentPen
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)
31 BlackForegroundPen
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
)
33 NormalFont
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)
40 class ShapeTextLine(object):
41 def __init__(self
, the_x
, the_y
, the_line
):
58 def SetText(self
, text
):
66 class ShapeEvtHandler(object):
67 def __init__(self
, prev
= None, shape
= None):
68 self
._previousHandler
= prev
69 self
._handlerShape
= shape
74 def SetShape(self
, sh
):
75 self
._handlerShape
= sh
78 return self
._handlerShape
80 def SetPreviousHandler(self
, handler
):
81 self
._previousHandler
= handler
83 def GetPreviousHandler(self
):
84 return self
._previousHandler
87 if self
._previousHandler
:
88 self
._previousHandler
.OnDraw(dc
)
90 def OnMoveLinks(self
, dc
):
91 if self
._previousHandler
:
92 self
._previousHandler
.OnMoveLinks(dc
)
94 def OnMoveLink(self
, dc
, moveControlPoints
= True):
95 if self
._previousHandler
:
96 self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
)
98 def OnDrawContents(self
, dc
):
99 if self
._previousHandler
:
100 self
._previousHandler
.OnDrawContents(dc
)
102 def OnDrawBranches(self
, dc
, erase
= False):
103 if self
._previousHandler
:
104 self
._previousHandler
.OnDrawBranches(dc
, erase
= erase
)
106 def OnSize(self
, x
, y
):
107 if self
._previousHandler
:
108 self
._previousHandler
.OnSize(x
, y
)
110 def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
111 if self
._previousHandler
:
112 return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
)
116 def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
117 if self
._previousHandler
:
118 return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
)
122 def OnErase(self
, dc
):
123 if self
._previousHandler
:
124 self
._previousHandler
.OnErase(dc
)
126 def OnEraseContents(self
, dc
):
127 if self
._previousHandler
:
128 self
._previousHandler
.OnEraseContents(dc
)
130 def OnHighlight(self
, dc
):
131 if self
._previousHandler
:
132 self
._previousHandler
.OnHighlight(dc
)
134 def OnLeftClick(self
, x
, y
, keys
, attachment
):
135 if self
._previousHandler
:
136 self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
)
138 def OnLeftDoubleClick(self
, x
, y
, keys
= 0, attachment
= 0):
139 if self
._previousHandler
:
140 self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
)
142 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
143 if self
._previousHandler
:
144 self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
)
146 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
147 if self
._previousHandler
:
148 self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
)
150 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
151 if self
._previousHandler
:
152 self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
)
154 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
155 if self
._previousHandler
:
156 self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
)
158 def OnDragRight(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
159 if self
._previousHandler
:
160 self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
)
162 def OnBeginDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
163 if self
._previousHandler
:
164 self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
)
166 def OnEndDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
167 if self
._previousHandler
:
168 self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
)
170 # Control points ('handles') redirect control to the actual shape,
171 # to make it easier to override sizing behaviour.
172 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
173 if self
._previousHandler
:
174 self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
)
176 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
177 if self
._previousHandler
:
178 self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
)
180 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
181 if self
._previousHandler
:
182 self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
)
184 def OnBeginSize(self
, w
, h
):
187 def OnEndSize(self
, w
, h
):
190 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
191 if self
._previousHandler
:
192 self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
)
194 def OnDrawControlPoints(self
, dc
):
195 if self
._previousHandler
:
196 self
._previousHandler
.OnDrawControlPoints(dc
)
198 def OnEraseControlPoints(self
, dc
):
199 if self
._previousHandler
:
200 self
._previousHandler
.OnEraseControlPoints(dc
)
202 # Can override this to prevent or intercept line reordering.
203 def OnChangeAttachment(self
, attachment
, line
, ordering
):
204 if self
._previousHandler
:
205 self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
)
209 class Shape(ShapeEvtHandler
):
214 The wxShape is the top-level, abstract object that all other objects
215 are derived from. All common functionality is represented by wxShape's
216 members, and overriden members that appear in derived classes and have
217 behaviour as documented for wxShape, are not documented separately.
220 GraphicsInSizeToContents
= False
222 def __init__(self
, canvas
= None):
223 ShapeEvtHandler
.__init
__(self
)
225 self
._eventHandler
= self
228 self
._formatted
= False
229 self
._canvas
= canvas
232 self
._pen
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
)
233 self
._brush
= wx
.WHITE_BRUSH
234 self
._font
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)
235 self
._textColour
= wx
.BLACK
236 self
._textColourName
= wx
.BLACK
237 self
._visible
= False
238 self
._selected
= False
239 self
._attachmentMode
= ATTACHMENT_MODE_NONE
240 self
._spaceAttachments
= True
241 self
._disableLabel
= False
242 self
._fixedWidth
= False
243 self
._fixedHeight
= False
244 self
._drawHandles
= True
245 self
._sensitivity
= OP_ALL
246 self
._draggable
= True
248 self
._formatMode
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
249 self
._shadowMode
= SHADOW_NONE
250 self
._shadowOffsetX
= 6
251 self
._shadowOffsetY
= 6
252 self
._shadowBrush
= wx
.BLACK_BRUSH
253 self
._textMarginX
= 5
254 self
._textMarginY
= 5
255 self
._regionName
= "0"
256 self
._centreResize
= True
257 self
._maintainAspectRatio
= False
258 self
._highlighted
= False
260 self
._branchNeckLength
= 10
261 self
._branchStemLength
= 10
262 self
._branchSpacing
= 10
263 self
._branchStyle
= BRANCHING_ATTACHMENT_NORMAL
267 self
._controlPoints
= []
268 self
._attachmentPoints
= []
272 # Set up a default region. Much of the above will be put into
273 # the region eventually (the duplication is for compatibility)
274 region
= ShapeRegion()
276 region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
))
277 region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
)
278 region
.SetColour("BLACK")
279 self
._regions
.append(region
)
282 return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__)
284 def GetClassName(self
):
285 return str(self
.__class
__).split(".")[-1][:-2]
289 i
= self
._parent
.GetChildren().index(self
)
290 self
._parent
.GetChildren(i
).remove(self
)
294 self
.ClearAttachments()
297 self
._canvas
.RemoveShape(self
)
299 self
.GetEventHandler().OnDelete()
302 """TRUE if the shape may be dragged by the user."""
305 def SetShape(self
, sh
):
306 self
._handlerShape
= sh
309 """Get the internal canvas."""
312 def GetBranchStyle(self
):
313 return self
._branchStyle
315 def GetRotation(self
):
316 """Return the angle of rotation in radians."""
317 return self
._rotation
319 def SetRotation(self
, rotation
):
320 self
._rotation
= rotation
322 def SetHighlight(self
, hi
, recurse
= False):
323 """Set the highlight for a shape. Shape highlighting is unimplemented."""
324 self
._highlighted
= hi
326 for shape
in self
._children
:
327 shape
.SetHighlight(hi
, recurse
)
329 def SetSensitivityFilter(self
, sens
= OP_ALL
, recursive
= False):
330 """Set the shape to be sensitive or insensitive to specific mouse
333 sens is a bitlist of the following:
339 * OP_ALL (equivalent to a combination of all the above).
341 self
._draggable
= sens
& OP_DRAG_LEFT
343 self
._sensitivity
= sens
345 for shape
in self
._children
:
346 shape
.SetSensitivityFilter(sens
, True)
348 def SetDraggable(self
, drag
, recursive
= False):
349 """Set the shape to be draggable or not draggable."""
350 self
._draggable
= drag
352 self
._sensitivity |
= OP_DRAG_LEFT
353 elif self
._sensitivity
& OP_DRAG_LEFT
:
354 self
._sensitivity
-= OP_DRAG_LEFT
357 for shape
in self
._children
:
358 shape
.SetDraggable(drag
, True)
360 def SetDrawHandles(self
, drawH
):
361 """Set the drawHandles flag for this shape and all descendants.
362 If drawH is TRUE (the default), any handles (control points) will
363 be drawn. Otherwise, the handles will not be drawn.
365 self
._drawHandles
= drawH
366 for shape
in self
._children
:
367 shape
.SetDrawHandles(drawH
)
369 def SetShadowMode(self
, mode
, redraw
= False):
370 """Set the shadow mode (whether a shadow is drawn or not).
371 mode can be one of the following:
374 No shadow (the default).
376 Shadow on the left side.
378 Shadow on the right side.
380 if redraw
and self
.GetCanvas():
381 dc
= wx
.ClientDC(self
.GetCanvas())
382 self
.GetCanvas().PrepareDC(dc
)
384 self
._shadowMode
= mode
387 self
._shadowMode
= mode
389 def SetCanvas(self
, theCanvas
):
390 """Identical to Shape.Attach."""
391 self
._canvas
= theCanvas
392 for shape
in self
._children
:
393 shape
.SetCanvas(theCanvas
)
395 def AddToCanvas(self
, theCanvas
, addAfter
= None):
396 """Add the shape to the canvas's shape list.
397 If addAfter is non-NULL, will add the shape after this one.
399 theCanvas
.AddShape(self
, addAfter
)
402 for object in self
._children
:
403 object.AddToCanvas(theCanvas
, lastImage
)
406 def InsertInCanvas(self
, theCanvas
):
407 """Insert the shape at the front of the shape list of canvas."""
408 theCanvas
.InsertShape(self
)
411 for object in self
._children
:
412 object.AddToCanvas(theCanvas
, lastImage
)
415 def RemoveFromCanvas(self
, theCanvas
):
416 """Remove the shape from the canvas."""
419 theCanvas
.RemoveShape(self
)
420 for object in self
._children
:
421 object.RemoveFromCanvas(theCanvas
)
423 def ClearAttachments(self
):
424 """Clear internal custom attachment point shapes (of class
427 self
._attachmentPoints
= []
429 def ClearText(self
, regionId
= 0):
430 """Clear the text from the specified text region."""
433 if regionId
< len(self
._regions
):
434 self
._regions
[regionId
].ClearText()
436 def ClearRegions(self
):
437 """Clear the ShapeRegions from the shape."""
440 def AddRegion(self
, region
):
441 """Add a region to the shape."""
442 self
._regions
.append(region
)
444 def SetDefaultRegionSize(self
):
445 """Set the default region to be consistent with the shape size."""
446 if not self
._regions
:
448 w
, h
= self
.GetBoundingBoxMax()
449 self
._regions
[0].SetSize(w
, h
)
451 def HitTest(self
, x
, y
):
452 """Given a point on a canvas, returns TRUE if the point was on the
453 shape, and returns the nearest attachment point and distance from
454 the given point and target.
456 width
, height
= self
.GetBoundingBoxMax()
462 width
+= 4 # Allowance for inaccurate mousing
465 left
= self
._xpos
- width
/ 2.0
466 top
= self
._ypos
- height
/ 2.0
467 right
= self
._xpos
+ width
/ 2.0
468 bottom
= self
._ypos
+ height
/ 2.0
470 nearest_attachment
= 0
472 # If within the bounding box, check the attachment points
474 if x
>= left
and x
<= right
and y
>= top
and y
<= bottom
:
475 n
= self
.GetNumberOfAttachments()
478 # GetAttachmentPosition[Edge] takes a logical attachment position,
479 # i.e. if it's rotated through 90%, position 0 is East-facing.
482 e
= self
.GetAttachmentPositionEdge(i
)
485 l
= math
.sqrt(((xp
- x
) * (xp
- x
)) + (yp
- y
) * (yp
- y
))
488 nearest_attachment
= i
490 return nearest_attachment
, nearest
493 # Format a text string according to the region size, adding
494 # strings with positions to region text list
496 def FormatText(self
, dc
, s
, i
= 0):
497 """Reformat the given text region; defaults to formatting the
502 if not self
._regions
:
505 if i
> len(self
._regions
):
508 region
= self
._regions
[i
]
509 region
._regionText
= s
510 dc
.SetFont(region
.GetFont())
512 w
, h
= region
.GetSize()
514 stringList
= FormatText(dc
, s
, (w
- 2 * self
._textMarginX
), (h
- 2 * self
._textMarginY
), region
.GetFormatMode())
516 line
= ShapeTextLine(0.0, 0.0, s
)
517 region
.GetFormattedText().append(line
)
521 # Don't try to resize an object with more than one image (this
522 # case should be dealt with by overriden handlers)
523 if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
524 region
.GetFormattedText().GetCount() and \
525 len(self
._regions
) == 1 and \
526 not Shape
.GraphicsInSizeToContents
:
528 actualW
, actualH
= GetCentredTextExtent(dc
, region
.GetFormattedText())
529 if actualW
+ 2 * self
._textMarginX
!= w
or actualH
+ 2 * self
._textMarginY
!= h
:
530 # If we are a descendant of a composite, must make sure
531 # the composite gets resized properly
533 topAncestor
= self
.GetTopAncestor()
534 if topAncestor
!= self
:
535 Shape
.GraphicsInSizeToContents
= True
537 composite
= topAncestor
539 self
.SetSize(actualW
+ 2 * self
._textMarginX
, actualH
+ 2 * self
._textMarginY
)
540 self
.Move(dc
, self
._xpos
, self
._ypos
)
541 composite
.CalculateSize()
542 if composite
.Selected():
543 composite
.DeleteControlPoints(dc
)
544 composite
.MakeControlPoints()
545 composite
.MakeMandatoryControlPoints()
546 # Where infinite recursion might happen if we didn't stop it
548 Shape
.GraphicsInSizeToContents
= False
552 self
.SetSize(actualW
+ 2 * self
._textMarginX
, actualH
+ 2 * self
._textMarginY
)
553 self
.Move(dc
, self
._xpos
, self
._ypos
)
554 self
.EraseContents(dc
)
555 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
- 2 * self
._textMarginX
, actualH
- 2 * self
._textMarginY
, region
.GetFormatMode())
556 self
._formatted
= True
558 def Recentre(self
, dc
):
559 """Do recentring (or other formatting) for all the text regions
562 w
, h
= self
.GetBoundingBoxMin()
563 for region
in self
._regions
:
564 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
- 2 * self
._textMarginX
, h
- 2 * self
._textMarginY
, region
.GetFormatMode())
566 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
567 """Get the point at which the line from (x1, y1) to (x2, y2) hits
568 the shape. Returns False if the line doesn't hit the perimeter.
572 def SetPen(self
, the_pen
):
573 """Set the pen for drawing the shape's outline."""
576 def SetBrush(self
, the_brush
):
577 """Set the brush for filling the shape's shape."""
578 self
._brush
= the_brush
580 # Get the top - most (non-division) ancestor, or self
581 def GetTopAncestor(self
):
582 """Return the top-most ancestor of this shape (the root of
585 if not self
.GetParent():
588 if isinstance(self
.GetParent(), DivisionShape
):
590 return self
.GetParent().GetTopAncestor()
593 def SetFont(self
, the_font
, regionId
= 0):
594 """Set the font for the specified text region."""
595 self
._font
= the_font
596 if regionId
< len(self
._regions
):
597 self
._regions
[regionId
].SetFont(the_font
)
599 def GetFont(self
, regionId
= 0):
600 """Get the font for the specified text region."""
601 if regionId
>= len(self
._regions
):
603 return self
._regions
[regionId
].GetFont()
605 def SetFormatMode(self
, mode
, regionId
= 0):
606 """Set the format mode of the default text region. The argument
607 can be a bit list of the following:
616 if regionId
< len(self
._regions
):
617 self
._regions
[regionId
].SetFormatMode(mode
)
619 def GetFormatMode(self
, regionId
= 0):
620 if regionId
>= len(self
._regions
):
622 return self
._regions
[regionId
].GetFormatMode()
624 def SetTextColour(self
, the_colour
, regionId
= 0):
625 """Set the colour for the specified text region."""
626 self
._textColour
= wx
.TheColourDatabase
.Find(the_colour
)
627 self
._textColourName
= the_colour
629 if regionId
< len(self
._regions
):
630 self
._regions
[regionId
].SetColour(the_colour
)
632 def GetTextColour(self
, regionId
= 0):
633 """Get the colour for the specified text region."""
634 if regionId
>= len(self
._regions
):
636 return self
._regions
[regionId
].GetTextColour()
638 def SetRegionName(self
, name
, regionId
= 0):
639 """Set the name for this region.
640 The name for a region is unique within the scope of the whole
641 composite, whereas a region id is unique only for a single image.
643 if regionId
< len(self
._regions
):
644 self
._regions
[regionId
].SetName(name
)
646 def GetRegionName(self
, regionId
= 0):
647 """Get the region's name.
648 A region's name can be used to uniquely determine a region within
649 an entire composite image hierarchy. See also Shape.SetRegionName.
651 if regionId
>= len(self
._regions
):
653 return self
._regions
[regionId
].GetName()
655 def GetRegionId(self
, name
):
656 """Get the region's identifier by name.
657 This is not unique for within an entire composite, but is unique
660 for i
, r
in enumerate(self
._regions
):
661 if r
.GetName() == name
:
665 # Name all _regions in all subimages recursively
666 def NameRegions(self
, parentName
=""):
667 """Make unique names for all the regions in a shape or composite shape."""
668 n
= self
.GetNumberOfTextRegions()
671 buff
= parentName
+"."+str(i
)
674 self
.SetRegionName(buff
, i
)
676 for j
, child
in enumerate(self
._children
):
678 buff
= parentName
+"."+str(j
)
681 child
.NameRegions(buff
)
683 # Get a region by name, possibly looking recursively into composites
684 def FindRegion(self
, name
):
685 """Find the actual image ('this' if non-composite) and region id
686 for the given region name.
688 id = self
.GetRegionId(name
)
692 for child
in self
._children
:
693 actualImage
, regionId
= child
.FindRegion(name
)
695 return actualImage
, regionId
699 # Finds all region names for this image (composite or simple).
700 def FindRegionNames(self
):
701 """Get a list of all region names for this image (composite or simple)."""
703 n
= self
.GetNumberOfTextRegions()
705 list.append(self
.GetRegionName(i
))
707 for child
in self
._children
:
708 list += child
.FindRegionNames()
712 def AssignNewIds(self
):
713 """Assign new ids to this image and its children."""
714 self
._id
= wx
.NewId()
715 for child
in self
._children
:
718 def OnDraw(self
, dc
):
721 def OnMoveLinks(self
, dc
):
722 # Want to set the ends of all attached links
723 # to point to / from this object
725 for line
in self
._lines
:
726 line
.GetEventHandler().OnMoveLink(dc
)
728 def OnDrawContents(self
, dc
):
729 if not self
._regions
:
732 bound_x
, bound_y
= self
.GetBoundingBoxMin()
737 region
= self
._regions
[0]
739 dc
.SetFont(region
.GetFont())
741 dc
.SetTextForeground(region
.GetActualColourObject())
742 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
743 if not self
._formatted
:
744 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x
- 2 * self
._textMarginX
, bound_y
- 2 * self
._textMarginY
, region
.GetFormatMode())
745 self
._formatted
= True
747 if not self
.GetDisableLabel():
748 DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x
- 2 * self
._textMarginX
, bound_y
- 2 * self
._textMarginY
, region
.GetFormatMode())
751 def DrawContents(self
, dc
):
752 """Draw the internal graphic of the shape (such as text).
754 Do not override this function: override OnDrawContents, which
755 is called by this function.
757 self
.GetEventHandler().OnDrawContents(dc
)
759 def OnSize(self
, x
, y
):
762 def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
765 def OnErase(self
, dc
):
766 if not self
._visible
:
770 for line
in self
._lines
:
771 line
.GetEventHandler().OnErase(dc
)
773 self
.GetEventHandler().OnEraseContents(dc
)
775 def OnEraseContents(self
, dc
):
776 if not self
._visible
:
779 xp
, yp
= self
.GetX(), self
.GetY()
780 minX
, minY
= self
.GetBoundingBoxMin()
781 maxX
, maxY
= self
.GetBoundingBoxMax()
783 topLeftX
= xp
- maxX
/ 2.0 - 2
784 topLeftY
= yp
- maxY
/ 2.0 - 2
788 penWidth
= self
._pen
.GetWidth()
790 dc
.SetPen(self
.GetBackgroundPen())
791 dc
.SetBrush(self
.GetBackgroundBrush())
793 dc
.DrawRectangle(topLeftX
- penWidth
, topLeftY
- penWidth
, maxX
+ penWidth
* 2 + 4, maxY
+ penWidth
* 2 + 4)
795 def EraseLinks(self
, dc
, attachment
= -1, recurse
= False):
796 """Erase links attached to this shape, but do not repair damage
797 caused to other shapes.
799 if not self
._visible
:
802 for line
in self
._lines
:
803 if attachment
== -1 or (line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
):
804 line
.GetEventHandler().OnErase(dc
)
807 for child
in self
._children
:
808 child
.EraseLinks(dc
, attachment
, recurse
)
810 def DrawLinks(self
, dc
, attachment
= -1, recurse
= False):
811 """Draws any lines linked to this shape."""
812 if not self
._visible
:
815 for line
in self
._lines
:
816 if attachment
== -1 or (line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
):
817 line
.GetEventHandler().Draw(dc
)
820 for child
in self
._children
:
821 child
.DrawLinks(dc
, attachment
, recurse
)
823 # Returns TRUE if pt1 <= pt2 in the sense that one point comes before
824 # another on an edge of the shape.
825 # attachmentPoint is the attachment point (= side) in question.
827 # This is the default, rectangular implementation.
828 def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
):
829 """Return TRUE if pt1 is less than or equal to pt2, in the sense
830 that one point comes before another on an edge of the shape.
832 attachment is the attachment point (side) in question.
834 This function is used in Shape.MoveLineToNewAttachment to determine
835 the new line ordering.
837 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachmentPoint
)
838 if physicalAttachment
in [0, 2]:
839 return pt1
.x
<= pt2
.x
840 elif physicalAttachment
in [1, 3]:
841 return pt1
.y
<= pt2
.y
845 def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
):
846 """Move the given line (which must already be attached to the shape)
847 to a different attachment point on the shape, or a different order
848 on the same attachment.
850 Calls Shape.AttachmentSortTest and then
851 ShapeEvtHandler.OnChangeAttachment.
853 if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
:
856 # Is (x, y) on this object? If so, find the new attachment point
857 # the user has moved the point to
858 hit
= self
.HitTest(x
, y
)
862 newAttachment
, distance
= hit
866 if to_move
.GetTo() == self
:
867 oldAttachment
= to_move
.GetAttachmentTo()
869 oldAttachment
= to_move
.GetAttachmentFrom()
871 # The links in a new ordering
872 # First, add all links to the new list
873 newOrdering
= self
._lines
[:]
875 # Delete the line object from the list of links; we're going to move
876 # it to another position in the list
877 del newOrdering
[newOrdering
.index(to_move
)]
884 for line
in newOrdering
:
885 if line
.GetTo() == self
and oldAttachment
== line
.GetAttachmentTo() or \
886 line
.GetFrom() == self
and oldAttachment
== line
.GetAttachmentFrom():
887 startX
, startY
, endX
, endY
= line
.GetEnds()
888 if line
.GetTo() == self
:
895 thisPoint
= wx
.RealPoint(xp
, yp
)
896 lastPoint
= wx
.RealPoint(old_x
, old_y
)
897 newPoint
= wx
.RealPoint(x
, y
)
899 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
):
901 newOrdering
.insert(newOrdering
.index(line
), to_move
)
909 newOrdering
.append(to_move
)
911 self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
)
914 def OnChangeAttachment(self
, attachment
, line
, ordering
):
915 if line
.GetTo() == self
:
916 line
.SetAttachmentTo(attachment
)
918 line
.SetAttachmentFrom(attachment
)
920 self
.ApplyAttachmentOrdering(ordering
)
922 dc
= wx
.ClientDC(self
.GetCanvas())
923 self
.GetCanvas().PrepareDC(dc
)
926 if not self
.GetCanvas().GetQuickEditMode():
927 self
.GetCanvas().Redraw(dc
)
929 # Reorders the lines according to the given list
930 def ApplyAttachmentOrdering(self
, linesToSort
):
931 """Apply the line ordering in linesToSort to the shape, to reorder
932 the way lines are attached.
934 linesStore
= self
._lines
[:]
938 for line
in linesToSort
:
939 if line
in linesStore
:
940 del linesStore
[linesStore
.index(line
)]
941 self
._lines
.append(line
)
943 # Now add any lines that haven't been listed in linesToSort
944 self
._lines
+= linesStore
946 def SortLines(self
, attachment
, linesToSort
):
947 """ Reorder the lines coming into the node image at this attachment
948 position, in the order in which they appear in linesToSort.
950 Any remaining lines not in the list will be added to the end.
952 # This is a temporary store of all the lines at this attachment
953 # point. We'll tick them off as we've processed them.
954 linesAtThisAttachment
= []
956 for line
in self
._lines
[:]:
957 if line
.GetTo() == self
and line
.GetAttachmentTo() == attachment
or \
958 line
.GetFrom() == self
and line
.GetAttachmentFrom() == attachment
:
959 linesAtThisAttachment
.append(line
)
960 del self
._lines
[self
._lines
.index(line
)]
962 for line
in linesToSort
:
963 if line
in linesAtThisAttachment
:
965 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)]
966 self
._lines
.Append(line
)
968 # Now add any lines that haven't been listed in linesToSort
969 self
._lines
+= linesAtThisAttachment
971 def OnHighlight(self
, dc
):
974 def OnLeftClick(self
, x
, y
, keys
= 0, attachment
= 0):
975 if self
._sensitivity
& OP_CLICK_LEFT
!= OP_CLICK_LEFT
:
977 attachment
, dist
= self
._parent
.HitTest(x
, y
)
978 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
)
980 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
981 if self
._sensitivity
& OP_CLICK_RIGHT
!= OP_CLICK_RIGHT
:
982 attachment
, dist
= self
._parent
.HitTest(x
, y
)
983 self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
)
985 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
986 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
988 hit
= self
._parent
.HitTest(x
, y
)
990 attachment
, dist
= hit
991 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
)
994 dc
= wx
.ClientDC(self
.GetCanvas())
995 self
.GetCanvas().PrepareDC(dc
)
996 dc
.SetLogicalFunction(OGLRBLF
)
998 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
1000 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
1002 xx
= x
+ DragOffsetX
1003 yy
= y
+ DragOffsetY
1005 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1006 w
, h
= self
.GetBoundingBoxMax()
1007 self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
)
1009 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1010 global DragOffsetX
, DragOffsetY
1012 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1014 hit
= self
._parent
.HitTest(x
, y
)
1016 attachment
, dist
= hit
1017 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
)
1020 DragOffsetX
= self
._xpos
- x
1021 DragOffsetY
= self
._ypos
- y
1023 dc
= wx
.ClientDC(self
.GetCanvas())
1024 self
.GetCanvas().PrepareDC(dc
)
1026 # New policy: don't erase shape until end of drag.
1028 xx
= x
+ DragOffsetX
1029 yy
= y
+ DragOffsetY
1030 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1031 dc
.SetLogicalFunction(OGLRBLF
)
1033 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
1034 dc
.SetPen(dottedPen
)
1035 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
1037 w
, h
= self
.GetBoundingBoxMax()
1038 self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
)
1039 self
._canvas
.CaptureMouse()
1041 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1042 if self
._canvas
.HasCapture():
1043 self
._canvas
.ReleaseMouse()
1044 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1046 hit
= self
._parent
.HitTest(x
, y
)
1048 attachment
, dist
= hit
1049 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
)
1052 dc
= wx
.ClientDC(self
.GetCanvas())
1053 self
.GetCanvas().PrepareDC(dc
)
1055 dc
.SetLogicalFunction(wx
.COPY
)
1056 xx
= x
+ DragOffsetX
1057 yy
= y
+ DragOffsetY
1058 xx
, yy
= self
._canvas
.Snap(xx
, yy
)
1060 # New policy: erase shape at end of drag.
1063 self
.Move(dc
, xx
, yy
)
1064 if self
._canvas
and not self
._canvas
.GetQuickEditMode():
1065 self
._canvas
.Redraw(dc
)
1067 def OnDragRight(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
1068 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1070 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1071 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
)
1074 def OnBeginDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
1075 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1077 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1078 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
)
1081 def OnEndDragRight(self
, x
, y
, keys
= 0, attachment
= 0):
1082 if self
._sensitivity
& OP_DRAG_RIGHT
!= OP_DRAG_RIGHT
:
1084 attachment
, dist
= self
._parent
.HitTest(x
, y
)
1085 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
)
1088 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
1089 points
= [[x
- w
/ 2.0, y
- h
/ 2.0],
1090 [x
+ w
/ 2.0, y
- h
/ 2.0],
1091 [x
+ w
/ 2.0, y
+ h
/ 2.0],
1092 [x
- w
/ 2.0, y
+ h
/ 2.0],
1093 [x
- w
/ 2.0, y
- h
/ 2.0],
1096 dc
.DrawLines(points
)
1098 def Attach(self
, can
):
1099 """Set the shape's internal canvas pointer to point to the given canvas."""
1103 """Disassociates the shape from its canvas."""
1106 def Move(self
, dc
, x
, y
, display
= True):
1107 """Move the shape to the given position.
1108 Redraw if display is TRUE.
1113 if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
):
1116 self
._xpos
, self
._ypos
= x
, y
1118 self
.ResetControlPoints()
1125 self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
)
1127 def MoveLinks(self
, dc
):
1128 """Redraw all the lines attached to the shape."""
1129 self
.GetEventHandler().OnMoveLinks(dc
)
1132 """Draw the whole shape and any lines attached to it.
1134 Do not override this function: override OnDraw, which is called
1138 self
.GetEventHandler().OnDraw(dc
)
1139 self
.GetEventHandler().OnDrawContents(dc
)
1140 self
.GetEventHandler().OnDrawControlPoints(dc
)
1141 self
.GetEventHandler().OnDrawBranches(dc
)
1144 """Flash the shape."""
1145 if self
.GetCanvas():
1146 dc
= wx
.ClientDC(self
.GetCanvas())
1147 self
.GetCanvas
.PrepareDC(dc
)
1149 dc
.SetLogicalFunction(OGLRBLF
)
1151 dc
.SetLogicalFunction(wx
.COPY
)
1154 def Show(self
, show
):
1155 """Set a flag indicating whether the shape should be drawn."""
1156 self
._visible
= show
1157 for child
in self
._children
:
1160 def Erase(self
, dc
):
1162 Does not repair damage caused to other shapes.
1164 self
.GetEventHandler().OnErase(dc
)
1165 self
.GetEventHandler().OnEraseControlPoints(dc
)
1166 self
.GetEventHandler().OnDrawBranches(dc
, erase
= True)
1168 def EraseContents(self
, dc
):
1169 """Erase the shape contents, that is, the area within the shape's
1170 minimum bounding box.
1172 self
.GetEventHandler().OnEraseContents(dc
)
1174 def AddText(self
, string
):
1175 """Add a line of text to the shape's default text region."""
1176 if not self
._regions
:
1179 region
= self
._regions
[0]
1181 new_line
= ShapeTextLine(0, 0, string
)
1182 text
= region
.GetFormattedText()
1183 text
.append(new_line
)
1185 self
._formatted
= False
1187 def SetSize(self
, x
, y
, recursive
= True):
1188 """Set the shape's size."""
1189 self
.SetAttachmentSize(x
, y
)
1190 self
.SetDefaultRegionSize()
1192 def SetAttachmentSize(self
, w
, h
):
1193 width
, height
= self
.GetBoundingBoxMin()
1197 scaleX
= float(w
) / width
1201 scaleY
= float(h
) / height
1203 for point
in self
._attachmentPoints
:
1204 point
._x
= point
._x
* scaleX
1205 point
._y
= point
._y
* scaleY
1207 # Add line FROM this object
1208 def AddLine(self
, line
, other
, attachFrom
= 0, attachTo
= 0, positionFrom
= -1, positionTo
= -1):
1209 """Add a line between this shape and the given other shape, at the
1210 specified attachment points.
1212 The position in the list of lines at each end can also be specified,
1213 so that the line will be drawn at a particular point on its attachment
1216 if positionFrom
== -1:
1217 if not line
in self
._lines
:
1218 self
._lines
.append(line
)
1220 # Don't preserve old ordering if we have new ordering instructions
1222 self
._lines
.remove(line
)
1225 if positionFrom
< len(self
._lines
):
1226 self
._lines
.insert(positionFrom
, line
)
1228 self
._lines
.append(line
)
1230 if positionTo
== -1:
1231 if not other
in other
._lines
:
1232 other
._lines
.append(line
)
1234 # Don't preserve old ordering if we have new ordering instructions
1236 other
._lines
.remove(line
)
1239 if positionTo
< len(other
._lines
):
1240 other
._lines
.insert(positionTo
, line
)
1242 other
._lines
.append(line
)
1246 line
.SetAttachments(attachFrom
, attachTo
)
1248 dc
= wx
.ClientDC(self
._canvas
)
1249 self
._canvas
.PrepareDC(dc
)
1252 def RemoveLine(self
, line
):
1253 """Remove the given line from the shape's list of attached lines."""
1254 if line
.GetFrom() == self
:
1255 line
.GetTo()._lines
.remove(line
)
1257 line
.GetFrom()._lines
.remove(line
)
1259 self
._lines
.remove(line
)
1261 # Default - make 6 control points
1262 def MakeControlPoints(self
):
1263 """Make a list of control points (draggable handles) appropriate to
1266 maxX
, maxY
= self
.GetBoundingBoxMax()
1267 minX
, minY
= self
.GetBoundingBoxMin()
1269 widthMin
= minX
+ CONTROL_POINT_SIZE
+ 2
1270 heightMin
= minY
+ CONTROL_POINT_SIZE
+ 2
1272 # Offsets from main object
1273 top
= -heightMin
/ 2.0
1274 bottom
= heightMin
/ 2.0 + (maxY
- minY
)
1275 left
= -widthMin
/ 2.0
1276 right
= widthMin
/ 2.0 + (maxX
- minX
)
1278 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
)
1279 self
._canvas
.AddShape(control
)
1280 self
._controlPoints
.append(control
)
1282 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
)
1283 self
._canvas
.AddShape(control
)
1284 self
._controlPoints
.append(control
)
1286 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
)
1287 self
._canvas
.AddShape(control
)
1288 self
._controlPoints
.append(control
)
1290 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
)
1291 self
._canvas
.AddShape(control
)
1292 self
._controlPoints
.append(control
)
1294 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
)
1295 self
._canvas
.AddShape(control
)
1296 self
._controlPoints
.append(control
)
1298 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
)
1299 self
._canvas
.AddShape(control
)
1300 self
._controlPoints
.append(control
)
1302 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
)
1303 self
._canvas
.AddShape(control
)
1304 self
._controlPoints
.append(control
)
1306 control
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
)
1307 self
._canvas
.AddShape(control
)
1308 self
._controlPoints
.append(control
)
1310 def MakeMandatoryControlPoints(self
):
1311 """Make the mandatory control points.
1313 For example, the control point on a dividing line should appear even
1314 if the divided rectangle shape's handles should not appear (because
1315 it is the child of a composite, and children are not resizable).
1317 for child
in self
._children
:
1318 child
.MakeMandatoryControlPoints()
1320 def ResetMandatoryControlPoints(self
):
1321 """Reset the mandatory control points."""
1322 for child
in self
._children
:
1323 child
.ResetMandatoryControlPoints()
1325 def ResetControlPoints(self
):
1326 """Reset the positions of the control points (for instance when the
1327 shape's shape has changed).
1329 self
.ResetMandatoryControlPoints()
1331 if len(self
._controlPoints
) == 0:
1334 maxX
, maxY
= self
.GetBoundingBoxMax()
1335 minX
, minY
= self
.GetBoundingBoxMin()
1337 widthMin
= minX
+ CONTROL_POINT_SIZE
+ 2
1338 heightMin
= minY
+ CONTROL_POINT_SIZE
+ 2
1340 # Offsets from main object
1341 top
= -heightMin
/ 2.0
1342 bottom
= heightMin
/ 2.0 + (maxY
- minY
)
1343 left
= -widthMin
/ 2.0
1344 right
= widthMin
/ 2.0 + (maxX
- minX
)
1346 self
._controlPoints
[0]._xoffset
= left
1347 self
._controlPoints
[0]._yoffset
= top
1349 self
._controlPoints
[1]._xoffset
= 0
1350 self
._controlPoints
[1]._yoffset
= top
1352 self
._controlPoints
[2]._xoffset
= right
1353 self
._controlPoints
[2]._yoffset
= top
1355 self
._controlPoints
[3]._xoffset
= right
1356 self
._controlPoints
[3]._yoffset
= 0
1358 self
._controlPoints
[4]._xoffset
= right
1359 self
._controlPoints
[4]._yoffset
= bottom
1361 self
._controlPoints
[5]._xoffset
= 0
1362 self
._controlPoints
[5]._yoffset
= bottom
1364 self
._controlPoints
[6]._xoffset
= left
1365 self
._controlPoints
[6]._yoffset
= bottom
1367 self
._controlPoints
[7]._xoffset
= left
1368 self
._controlPoints
[7]._yoffset
= 0
1370 def DeleteControlPoints(self
, dc
= None):
1371 """Delete the control points (or handles) for the shape.
1373 Does not redraw the shape.
1375 for control
in self
._controlPoints
:
1377 control
.GetEventHandler().OnErase(dc
)
1378 self
._canvas
.RemoveShape(control
)
1380 self
._controlPoints
= []
1382 # Children of divisions are contained objects,
1384 if not isinstance(self
, DivisionShape
):
1385 for child
in self
._children
:
1386 child
.DeleteControlPoints(dc
)
1388 def OnDrawControlPoints(self
, dc
):
1389 if not self
._drawHandles
:
1392 dc
.SetBrush(wx
.BLACK_BRUSH
)
1393 dc
.SetPen(wx
.BLACK_PEN
)
1395 for control
in self
._controlPoints
:
1398 # Children of divisions are contained objects,
1400 # This test bypasses the type facility for speed
1401 # (critical when drawing)
1403 if not isinstance(self
, DivisionShape
):
1404 for child
in self
._children
:
1405 child
.GetEventHandler().OnDrawControlPoints(dc
)
1407 def OnEraseControlPoints(self
, dc
):
1408 for control
in self
._controlPoints
:
1411 if not isinstance(self
, DivisionShape
):
1412 for child
in self
._children
:
1413 child
.GetEventHandler().OnEraseControlPoints(dc
)
1415 def Select(self
, select
, dc
= None):
1416 """Select or deselect the given shape, drawing or erasing control points
1417 (handles) as necessary.
1419 self
._selected
= select
1421 self
.MakeControlPoints()
1422 # Children of divisions are contained objects,
1424 if not isinstance(self
, DivisionShape
):
1425 for child
in self
._children
:
1426 child
.MakeMandatoryControlPoints()
1428 self
.GetEventHandler().OnDrawControlPoints(dc
)
1430 self
.DeleteControlPoints(dc
)
1431 if not isinstance(self
, DivisionShape
):
1432 for child
in self
._children
:
1433 child
.DeleteControlPoints(dc
)
1436 """TRUE if the shape is currently selected."""
1437 return self
._selected
1439 def AncestorSelected(self
):
1440 """TRUE if the shape's ancestor is currently selected."""
1443 if not self
.GetParent():
1445 return self
.GetParent().AncestorSelected()
1447 def GetNumberOfAttachments(self
):
1448 """Get the number of attachment points for this shape."""
1449 # Should return the MAXIMUM attachment point id here,
1450 # so higher-level functions can iterate through all attachments,
1451 # even if they're not contiguous.
1453 if len(self
._attachmentPoints
) == 0:
1457 for point
in self
._attachmentPoints
:
1458 if point
._id
> maxN
:
1462 def AttachmentIsValid(self
, attachment
):
1463 """TRUE if attachment is a valid attachment point."""
1464 if len(self
._attachmentPoints
) == 0:
1465 return attachment
in range(4)
1467 for point
in self
._attachmentPoints
:
1468 if point
._id
== attachment
:
1472 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
1473 """Get the position at which the given attachment point should be drawn.
1475 If attachment isn't found among the attachment points of the shape,
1478 if self
._attachmentMode
== ATTACHMENT_MODE_NONE
:
1479 return self
._xpos
, self
._ypos
1480 elif self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
1481 pt
, stemPt
= self
.GetBranchingAttachmentPoint(attachment
, nth
)
1483 elif self
._attachmentMode
== ATTACHMENT_MODE_EDGE
:
1484 if len(self
._attachmentPoints
):
1485 for point
in self
._attachmentPoints
:
1486 if point
._id
== attachment
:
1487 return self
._xpos
+ point
._x
, self
._ypos
+ point
._y
1490 # Assume is rectangular
1491 w
, h
= self
.GetBoundingBoxMax()
1492 top
= self
._ypos
+ h
/ 2.0
1493 bottom
= self
._ypos
- h
/ 2.0
1494 left
= self
._xpos
- w
/ 2.0
1495 right
= self
._xpos
+ w
/ 2.0
1498 line
and line
.IsEnd(self
)
1500 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1503 if physicalAttachment
== 0:
1504 pt
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
)
1505 elif physicalAttachment
== 1:
1506 pt
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
)
1507 elif physicalAttachment
== 2:
1508 pt
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
)
1509 elif physicalAttachment
== 3:
1510 pt
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
)
1516 def GetBoundingBoxMax(self
):
1517 """Get the maximum bounding box for the shape, taking into account
1518 external features such as shadows.
1520 ww
, hh
= self
.GetBoundingBoxMin()
1521 if self
._shadowMode
!= SHADOW_NONE
:
1522 ww
+= self
._shadowOffsetX
1523 hh
+= self
._shadowOffsetY
1526 def GetBoundingBoxMin(self
):
1527 """Get the minimum bounding box for the shape, that defines the area
1528 available for drawing the contents (such as text).
1534 def HasDescendant(self
, image
):
1535 """TRUE if image is a descendant of this composite."""
1538 for child
in self
._children
:
1539 if child
.HasDescendant(image
):
1543 # Clears points from a list of wxRealPoints, and clears list
1544 # Useless in python? /pi
1545 def ClearPointList(self
, list):
1548 # Assuming the attachment lies along a vertical or horizontal line,
1549 # calculate the position on that point.
1550 def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
):
1551 """Assuming the attachment lies along a vertical or horizontal line,
1552 calculate the position on that point.
1557 The first point of the line repesenting the edge of the shape.
1560 The second point of the line representing the edge of the shape.
1563 The position on the edge (for example there may be 6 lines at
1564 this attachment point, and this may be the 2nd line.
1567 The number of lines at this edge.
1574 This function expects the line to be either vertical or horizontal,
1575 and determines which.
1577 isEnd
= line
and line
.IsEnd(self
)
1579 # Are we horizontal or vertical?
1580 isHorizontal
= RoughlyEqual(pt1
[1], pt2
[1])
1590 if self
._spaceAttachments
:
1591 if line
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
:
1592 # Align line according to the next handle along
1593 point
= line
.GetNextControlPoint(self
)
1594 if point
[0] < firstPoint
[0]:
1596 elif point
[0] > secondPoint
[0]:
1601 x
= firstPoint
[0] + (nth
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs
+ 1.0)
1603 x
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint
1606 assert RoughlyEqual(pt1
[0], pt2
[0])
1615 if self
._spaceAttachments
:
1616 if line
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
:
1617 # Align line according to the next handle along
1618 point
= line
.GetNextControlPoint(self
)
1619 if point
[1] < firstPoint
[1]:
1621 elif point
[1] > secondPoint
[1]:
1626 y
= firstPoint
[1] + (nth
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs
+ 1.0)
1628 y
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint
1633 # Return the zero-based position in m_lines of line
1634 def GetLinePosition(self
, line
):
1635 """Get the zero-based position of line in the list of lines
1639 return self
._lines
.index(line
)
1647 # shoulder1 ->---------<- shoulder2
1649 # <- branching attachment point N-1
1651 def GetBranchingAttachmentInfo(self
, attachment
):
1652 """Get information about where branching connections go.
1654 Returns FALSE if there are no lines at this attachment.
1656 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1658 # Number of lines at this attachment
1659 lineCount
= self
.GetAttachmentLineCount(attachment
)
1664 totalBranchLength
= self
._branchSpacing
* (lineCount
- 1)
1665 root
= self
.GetBranchingAttachmentRoot(attachment
)
1667 neck
= wx
.RealPoint()
1668 shoulder1
= wx
.RealPoint()
1669 shoulder2
= wx
.RealPoint()
1671 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1672 if physicalAttachment
== 0:
1673 neck
.x
= self
.GetX()
1674 neck
.y
= root
.y
- self
._branchNeckLength
1676 shoulder1
.x
= root
.x
- totalBranchLength
/ 2.0
1677 shoulder2
.x
= root
.x
+ totalBranchLength
/ 2.0
1679 shoulder1
.y
= neck
.y
1680 shoulder2
.y
= neck
.y
1681 elif physicalAttachment
== 1:
1682 neck
.x
= root
.x
+ self
._branchNeckLength
1685 shoulder1
.x
= neck
.x
1686 shoulder2
.x
= neck
.x
1688 shoulder1
.y
= neck
.y
- totalBranchLength
/ 2.0
1689 shoulder1
.y
= neck
.y
+ totalBranchLength
/ 2.0
1690 elif physicalAttachment
== 2:
1691 neck
.x
= self
.GetX()
1692 neck
.y
= root
.y
+ self
._branchNeckLength
1694 shoulder1
.x
= root
.x
- totalBranchLength
/ 2.0
1695 shoulder2
.x
= root
.x
+ totalBranchLength
/ 2.0
1697 shoulder1
.y
= neck
.y
1698 shoulder2
.y
= neck
.y
1699 elif physicalAttachment
== 3:
1700 neck
.x
= root
.x
- self
._branchNeckLength
1703 shoulder1
.x
= neck
.x
1704 shoulder2
.x
= neck
.x
1706 shoulder1
.y
= neck
.y
- totalBranchLength
/ 2.0
1707 shoulder2
.y
= neck
.y
+ totalBranchLength
/ 2.0
1709 raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
1710 return root
, neck
, shoulder1
, shoulder2
1712 def GetBranchingAttachmentPoint(self
, attachment
, n
):
1713 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1715 root
, neck
, shoulder1
, shoulder2
= self
.GetBranchingAttachmentInfo(attachment
)
1717 stemPt
= wx
.RealPoint()
1719 if physicalAttachment
== 0:
1720 pt
.y
= neck
.y
- self
._branchStemLength
1721 pt
.x
= shoulder1
.x
+ n
* self
._branchSpacing
1725 elif physicalAttachment
== 2:
1726 pt
.y
= neck
.y
+ self
._branchStemLength
1727 pt
.x
= shoulder1
.x
+ n
* self
._branchStemLength
1731 elif physicalAttachment
== 1:
1732 pt
.x
= neck
.x
+ self
._branchStemLength
1733 pt
.y
= shoulder1
.y
+ n
* self
._branchSpacing
1737 elif physicalAttachment
== 3:
1738 pt
.x
= neck
.x
- self
._branchStemLength
1739 pt
.y
= shoulder1
.y
+ n
* self
._branchSpacing
1744 raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
1748 def GetAttachmentLineCount(self
, attachment
):
1749 """Get the number of lines at this attachment position."""
1751 for lineShape
in self
._lines
:
1752 if lineShape
.GetFrom() == self
and lineShape
.GetAttachmentFrom() == attachment
:
1754 elif lineShape
.GetTo() == self
and lineShape
.GetAttachmentTo() == attachment
:
1758 def GetBranchingAttachmentRoot(self
, attachment
):
1759 """Get the root point at the given attachment."""
1760 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
1762 root
= wx
.RealPoint()
1764 width
, height
= self
.GetBoundingBoxMax()
1766 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1767 if physicalAttachment
== 0:
1768 root
.x
= self
.GetX()
1769 root
.y
= self
.GetY() - height
/ 2.0
1770 elif physicalAttachment
== 1:
1771 root
.x
= self
.GetX() + width
/ 2.0
1772 root
.y
= self
.GetY()
1773 elif physicalAttachment
== 2:
1774 root
.x
= self
.GetX()
1775 root
.y
= self
.GetY() + height
/ 2.0
1776 elif physicalAttachment
== 3:
1777 root
.x
= self
.GetX() - width
/ 2.0
1778 root
.y
= self
.GetY()
1780 raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
1784 # Draw or erase the branches (not the actual arcs though)
1785 def OnDrawBranchesAttachment(self
, dc
, attachment
, erase
= False):
1786 count
= self
.GetAttachmentLineCount(attachment
)
1790 root
, neck
, shoulder1
, shoulder2
= self
.GetBranchingAttachmentInfo(attachment
)
1793 dc
.SetPen(wx
.WHITE_PEN
)
1794 dc
.SetBrush(wx
.WHITE_BRUSH
)
1796 dc
.SetPen(wx
.BLACK_PEN
)
1797 dc
.SetBrush(wx
.BLACK_BRUSH
)
1800 dc
.DrawLine(root
, neck
)
1803 # Draw shoulder-to-shoulder line
1804 dc
.DrawLine(shoulder1
, shoulder2
)
1805 # Draw all the little branches
1806 for i
in range(count
):
1807 pt
, stemPt
= self
.GetBranchingAttachmentPoint(attachment
, i
)
1808 dc
.DrawLine(stemPt
, pt
)
1810 if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB
and count
> 1:
1812 dc
.DrawEllipse(stemPt
.x
- blobSize
/ 2.0, stemPt
.y
- blobSize
/ 2.0, blobSize
, blobSize
)
1814 def OnDrawBranches(self
, dc
, erase
= False):
1815 if self
._attachmentMode
!= ATTACHMENT_MODE_BRANCHING
:
1817 for i
in range(self
.GetNumberOfAttachments()):
1818 self
.OnDrawBranchesAttachment(dc
, i
, erase
)
1820 def GetAttachmentPositionEdge(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
1821 """ Only get the attachment position at the _edge_ of the shape,
1822 ignoring branching mode. This is used e.g. to indicate the edge of
1823 interest, not the point on the attachment branch.
1825 oldMode
= self
._attachmentMode
1827 # Calculate as if to edge, not branch
1828 if self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
1829 self
._attachmentMode
= ATTACHMENT_MODE_EDGE
1830 res
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
)
1831 self
._attachmentMode
= oldMode
1835 def PhysicalToLogicalAttachment(self
, physicalAttachment
):
1836 """ Rotate the standard attachment point from physical
1837 (0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
1839 if RoughlyEqual(self
.GetRotation(), 0):
1840 i
= physicalAttachment
1841 elif RoughlyEqual(self
.GetRotation(), math
.pi
/ 2.0):
1842 i
= physicalAttachment
- 1
1843 elif RoughlyEqual(self
.GetRotation(), math
.pi
):
1844 i
= physicalAttachment
- 2
1845 elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi
/ 2.0):
1846 i
= physicalAttachment
- 3
1848 # Can't handle -- assume the same
1849 return physicalAttachment
1856 def LogicalToPhysicalAttachment(self
, logicalAttachment
):
1857 """Rotate the standard attachment point from logical
1858 to physical (0 is always North).
1860 if RoughlyEqual(self
.GetRotation(), 0):
1861 i
= logicalAttachment
1862 elif RoughlyEqual(self
.GetRotation(), math
.pi
/ 2.0):
1863 i
= logicalAttachment
+ 1
1864 elif RoughlyEqual(self
.GetRotation(), math
.pi
):
1865 i
= logicalAttachment
+ 2
1866 elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi
/ 2.0):
1867 i
= logicalAttachment
+ 3
1869 return logicalAttachment
1876 def Rotate(self
, x
, y
, theta
):
1877 """Rotate about the given axis by the given amount in radians."""
1878 self
._rotation
= theta
1879 if self
._rotation
< 0:
1880 self
._rotation
+= 2 * math
.pi
1881 elif self
._rotation
> 2 * math
.pi
:
1882 self
._rotation
-= 2 * math
.pi
1884 def GetBackgroundPen(self
):
1885 """Return pen of the right colour for the background."""
1886 if self
.GetCanvas():
1887 return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
)
1888 return WhiteBackgroundPen
1890 def GetBackgroundBrush(self
):
1891 """Return brush of the right colour for the background."""
1892 if self
.GetCanvas():
1893 return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
)
1894 return WhiteBackgroundBrush
1897 """Get the x position of the centre of the shape."""
1901 """Get the y position of the centre of the shape."""
1905 """Set the x position of the shape."""
1909 """Set the y position of the shape."""
1912 def GetParent(self
):
1913 """Return the parent of this shape, if it is part of a composite."""
1916 def SetParent(self
, p
):
1919 def GetChildren(self
):
1920 """Return the list of children for this shape."""
1921 return self
._children
1923 def GetDrawHandles(self
):
1924 """Return the list of drawhandles."""
1925 return self
._drawHandles
1927 def GetEventHandler(self
):
1928 """Return the event handler for this shape."""
1929 return self
._eventHandler
1931 def SetEventHandler(self
, handler
):
1932 """Set the event handler for this shape."""
1933 self
._eventHandler
= handler
1935 def Recompute(self
):
1936 """Recomputes any constraints associated with the shape.
1938 Normally applicable to CompositeShapes only, but harmless for
1939 other classes of Shape.
1943 def IsHighlighted(self
):
1944 """TRUE if the shape is highlighted. Shape highlighting is unimplemented."""
1945 return self
._highlighted
1947 def GetSensitivityFilter(self
):
1948 """Return the sensitivity filter, a bitlist of values.
1950 See Shape.SetSensitivityFilter.
1952 return self
._sensitivity
1954 def SetFixedSize(self
, x
, y
):
1955 """Set the shape to be fixed size."""
1956 self
._fixedWidth
= x
1957 self
._fixedHeight
= y
1959 def GetFixedSize(self
):
1960 """Return flags indicating whether the shape is of fixed size in
1963 return self
._fixedWidth
, self
._fixedHeight
1965 def GetFixedWidth(self
):
1966 """TRUE if the shape cannot be resized in the horizontal plane."""
1967 return self
._fixedWidth
1969 def GetFixedHeight(self
):
1970 """TRUE if the shape cannot be resized in the vertical plane."""
1971 return self
._fixedHeight
1973 def SetSpaceAttachments(self
, sp
):
1974 """Indicate whether lines should be spaced out evenly at the point
1975 they touch the node (sp = True), or whether they should join at a single
1978 self
._spaceAttachments
= sp
1980 def GetSpaceAttachments(self
):
1981 """Return whether lines should be spaced out evenly at the point they
1982 touch the node (True), or whether they should join at a single point
1985 return self
._spaceAttachments
1987 def SetCentreResize(self
, cr
):
1988 """Specify whether the shape is to be resized from the centre (the
1989 centre stands still) or from the corner or side being dragged (the
1990 other corner or side stands still).
1992 self
._centreResize
= cr
1994 def GetCentreResize(self
):
1995 """TRUE if the shape is to be resized from the centre (the centre stands
1996 still), or FALSE if from the corner or side being dragged (the other
1997 corner or side stands still)
1999 return self
._centreResize
2001 def SetMaintainAspectRatio(self
, ar
):
2002 """Set whether a shape that resizes should not change the aspect ratio
2003 (width and height should be in the original proportion).
2005 self
._maintainAspectRatio
= ar
2007 def GetMaintainAspectRatio(self
):
2008 """TRUE if shape keeps aspect ratio during resize."""
2009 return self
._maintainAspectRatio
2012 """Return the list of lines connected to this shape."""
2015 def SetDisableLabel(self
, flag
):
2016 """Set flag to TRUE to stop the default region being shown."""
2017 self
._disableLabel
= flag
2019 def GetDisableLabel(self
):
2020 """TRUE if the default region will not be shown, FALSE otherwise."""
2021 return self
._disableLabel
2023 def SetAttachmentMode(self
, mode
):
2024 """Set the attachment mode.
2026 If TRUE, attachment points will be significant when drawing lines to
2027 and from this shape.
2028 If FALSE, lines will be drawn as if to the centre of the shape.
2030 self
._attachmentMode
= mode
2032 def GetAttachmentMode(self
):
2033 """Return the attachment mode.
2035 See Shape.SetAttachmentMode.
2037 return self
._attachmentMode
2040 """Set the integer identifier for this shape."""
2044 """Return the integer identifier for this shape."""
2048 """TRUE if the shape is in a visible state, FALSE otherwise.
2050 Note that this has nothing to do with whether the window is hidden
2051 or the shape has scrolled off the canvas; it refers to the internal
2054 return self
._visible
2057 """Return the pen used for drawing the shape's outline."""
2061 """Return the brush used for filling the shape."""
2064 def GetNumberOfTextRegions(self
):
2065 """Return the number of text regions for this shape."""
2066 return len(self
._regions
)
2068 def GetRegions(self
):
2069 """Return the list of ShapeRegions."""
2070 return self
._regions
2072 # Control points ('handles') redirect control to the actual shape, to
2073 # make it easier to override sizing behaviour.
2074 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
2075 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2077 dc
= wx
.ClientDC(self
.GetCanvas())
2078 self
.GetCanvas().PrepareDC(dc
)
2080 dc
.SetLogicalFunction(OGLRBLF
)
2082 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2083 dc
.SetPen(dottedPen
)
2084 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2086 if self
.GetCentreResize():
2087 # Maintain the same centre point
2088 new_width
= 2.0 * abs(x
- self
.GetX())
2089 new_height
= 2.0 * abs(y
- self
.GetY())
2091 # Constrain sizing according to what control point you're dragging
2092 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2093 if self
.GetMaintainAspectRatio():
2094 new_height
= bound_y
* (new_width
/ bound_x
)
2096 new_height
= bound_y
2097 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2098 if self
.GetMaintainAspectRatio():
2099 new_width
= bound_x
* (new_height
/ bound_y
)
2102 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
):
2103 new_height
= bound_y
* (new_width
/ bound_x
)
2105 if self
.GetFixedWidth():
2108 if self
.GetFixedHeight():
2109 new_height
= bound_y
2111 pt
._controlPointDragEndWidth
= new_width
2112 pt
._controlPointDragEndHeight
= new_height
2114 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
)
2116 # Don't maintain the same centre point
2117 newX1
= min(pt
._controlPointDragStartX
, x
)
2118 newY1
= min(pt
._controlPointDragStartY
, y
)
2119 newX2
= max(pt
._controlPointDragStartX
, x
)
2120 newY2
= max(pt
._controlPointDragStartY
, y
)
2121 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2122 newY1
= pt
._controlPointDragStartY
2123 newY2
= newY1
+ pt
._controlPointDragStartHeight
2124 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2125 newX1
= pt
._controlPointDragStartX
2126 newX2
= newX1
+ pt
._controlPointDragStartWidth
2127 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
or self
.GetMaintainAspectRatio()):
2128 newH
= (newX2
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
)
2129 if self
.GetY() > pt
._controlPointDragStartY
:
2130 newY2
= newY1
+ newH
2132 newY1
= newY2
- newH
2134 newWidth
= float(newX2
- newX1
)
2135 newHeight
= float(newY2
- newY1
)
2137 if pt
._type
== CONTROL_POINT_VERTICAL
and self
.GetMaintainAspectRatio():
2138 newWidth
= bound_x
* (newHeight
/ bound_y
)
2140 if pt
._type
== CONTROL_POINT_HORIZONTAL
and self
.GetMaintainAspectRatio():
2141 newHeight
= bound_y
* (newWidth
/ bound_x
)
2143 pt
._controlPointDragPosX
= newX1
+ newWidth
/ 2.0
2144 pt
._controlPointDragPosY
= newY1
+ newHeight
/ 2.0
2145 if self
.GetFixedWidth():
2148 if self
.GetFixedHeight():
2151 pt
._controlPointDragEndWidth
= newWidth
2152 pt
._controlPointDragEndHeight
= newHeight
2153 self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
)
2155 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2156 self
._canvas
.CaptureMouse()
2158 dc
= wx
.ClientDC(self
.GetCanvas())
2159 self
.GetCanvas().PrepareDC(dc
)
2161 dc
.SetLogicalFunction(OGLRBLF
)
2163 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2164 self
.GetEventHandler().OnEndSize(bound_x
, bound_y
)
2166 # Choose the 'opposite corner' of the object as the stationary
2167 # point in case this is non-centring resizing.
2168 if pt
.GetX() < self
.GetX():
2169 pt
._controlPointDragStartX
= self
.GetX() + bound_x
/ 2.0
2171 pt
._controlPointDragStartX
= self
.GetX() - bound_x
/ 2.0
2173 if pt
.GetY() < self
.GetY():
2174 pt
._controlPointDragStartY
= self
.GetY() + bound_y
/ 2.0
2176 pt
._controlPointDragStartY
= self
.GetY() - bound_y
/ 2.0
2178 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2179 pt
._controlPointDragStartY
= self
.GetY() - bound_y
/ 2.0
2180 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2181 pt
._controlPointDragStartX
= self
.GetX() - bound_x
/ 2.0
2183 # We may require the old width and height
2184 pt
._controlPointDragStartWidth
= bound_x
2185 pt
._controlPointDragStartHeight
= bound_y
2187 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2188 dc
.SetPen(dottedPen
)
2189 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2191 if self
.GetCentreResize():
2192 new_width
= 2.0 * abs(x
- self
.GetX())
2193 new_height
= 2.0 * abs(y
- self
.GetY())
2195 # Constrain sizing according to what control point you're dragging
2196 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2197 if self
.GetMaintainAspectRatio():
2198 new_height
= bound_y
* (new_width
/ bound_x
)
2200 new_height
= bound_y
2201 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2202 if self
.GetMaintainAspectRatio():
2203 new_width
= bound_x
* (new_height
/ bound_y
)
2206 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEY_SHIFT
):
2207 new_height
= bound_y
* (new_width
/ bound_x
)
2209 if self
.GetFixedWidth():
2212 if self
.GetFixedHeight():
2213 new_height
= bound_y
2215 pt
._controlPointDragEndWidth
= new_width
2216 pt
._controlPointDragEndHeight
= new_height
2217 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
)
2219 # Don't maintain the same centre point
2220 newX1
= min(pt
._controlPointDragStartX
, x
)
2221 newY1
= min(pt
._controlPointDragStartY
, y
)
2222 newX2
= max(pt
._controlPointDragStartX
, x
)
2223 newY2
= max(pt
._controlPointDragStartY
, y
)
2224 if pt
._type
== CONTROL_POINT_HORIZONTAL
:
2225 newY1
= pt
._controlPointDragStartY
2226 newY2
= newY1
+ pt
._controlPointDragStartHeight
2227 elif pt
._type
== CONTROL_POINT_VERTICAL
:
2228 newX1
= pt
._controlPointDragStartX
2229 newX2
= newX1
+ pt
._controlPointDragStartWidth
2230 elif pt
._type
== CONTROL_POINT_DIAGONAL
and (keys
& KEYS
or self
.GetMaintainAspectRatio()):
2231 newH
= (newX2
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
)
2232 if pt
.GetY() > pt
._controlPointDragStartY
:
2233 newY2
= newY1
+ newH
2235 newY1
= newY2
- newH
2237 newWidth
= float(newX2
- newX1
)
2238 newHeight
= float(newY2
- newY1
)
2240 if pt
._type
== CONTROL_POINT_VERTICAL
and self
.GetMaintainAspectRatio():
2241 newWidth
= bound_x
* (newHeight
/ bound_y
)
2243 if pt
._type
== CONTROL_POINT_HORIZONTAL
and self
.GetMaintainAspectRatio():
2244 newHeight
= bound_y
* (newWidth
/ bound_x
)
2246 pt
._controlPointDragPosX
= newX1
+ newWidth
/ 2.0
2247 pt
._controlPointDragPosY
= newY1
+ newHeight
/ 2.0
2248 if self
.GetFixedWidth():
2251 if self
.GetFixedHeight():
2254 pt
._controlPointDragEndWidth
= newWidth
2255 pt
._controlPointDragEndHeight
= newHeight
2256 self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
)
2258 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2259 dc
= wx
.ClientDC(self
.GetCanvas())
2260 self
.GetCanvas().PrepareDC(dc
)
2262 if self
._canvas
.HasCapture():
2263 self
._canvas
.ReleaseMouse()
2264 dc
.SetLogicalFunction(wx
.COPY
)
2266 self
.ResetControlPoints()
2270 self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
)
2272 # The next operation could destroy this control point (it does for
2273 # label objects, via formatting the text), so save all values we're
2274 # going to use, or we'll be accessing garbage.
2278 if self
.GetCentreResize():
2279 self
.Move(dc
, self
.GetX(), self
.GetY())
2281 self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
)
2283 # Recursively redraw links if we have a composite
2284 if len(self
.GetChildren()):
2285 self
.DrawLinks(dc
, -1, True)
2287 width
, height
= self
.GetBoundingBoxMax()
2288 self
.GetEventHandler().OnEndSize(width
, height
)
2290 if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
:
2291 self
._canvas
.Redraw(dc
)
2295 class RectangleShape(Shape
):
2297 The wxRectangleShape has rounded or square corners.
2302 def __init__(self
, w
= 0.0, h
= 0.0):
2303 Shape
.__init
__(self
)
2306 self
._cornerRadius
= 0.0
2307 self
.SetDefaultRegionSize()
2309 def OnDraw(self
, dc
):
2310 x1
= self
._xpos
- self
._width
/ 2.0
2311 y1
= self
._ypos
- self
._height
/ 2.0
2313 if self
._shadowMode
!= SHADOW_NONE
:
2314 if self
._shadowBrush
:
2315 dc
.SetBrush(self
._shadowBrush
)
2316 dc
.SetPen(TransparentPen
)
2318 if self
._cornerRadius
:
2319 dc
.DrawRoundedRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
)
2321 dc
.DrawRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
)
2324 if self
._pen
.GetWidth() == 0:
2325 dc
.SetPen(TransparentPen
)
2327 dc
.SetPen(self
._pen
)
2329 dc
.SetBrush(self
._brush
)
2331 if self
._cornerRadius
:
2332 dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
)
2334 dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
)
2336 def GetBoundingBoxMin(self
):
2337 return self
._width
, self
._height
2339 def SetSize(self
, x
, y
, recursive
= False):
2340 self
.SetAttachmentSize(x
, y
)
2341 self
._width
= max(x
, 1)
2342 self
._height
= max(y
, 1)
2343 self
.SetDefaultRegionSize()
2345 def GetCornerRadius(self
):
2346 """Get the radius of the rectangle's rounded corners."""
2347 return self
._cornerRadius
2349 def SetCornerRadius(self
, rad
):
2350 """Set the radius of the rectangle's rounded corners.
2352 If the radius is zero, a non-rounded rectangle will be drawn.
2353 If the radius is negative, the value is the proportion of the smaller
2354 dimension of the rectangle.
2356 self
._cornerRadius
= rad
2358 # Assume (x1, y1) is centre of box (most generally, line end at box)
2359 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2360 bound_x
, bound_y
= self
.GetBoundingBoxMax()
2361 return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
)
2366 def GetHeight(self
):
2369 def SetWidth(self
, w
):
2372 def SetHeight(self
, h
):
2377 class PolygonShape(Shape
):
2378 """A PolygonShape's shape is defined by a number of points passed to
2379 the object's constructor. It can be used to create new shapes such as
2380 diamonds and triangles.
2383 Shape
.__init
__(self
)
2386 self
._originalPoints
= None
2388 def Create(self
, the_points
= None):
2389 """Takes a list of wx.RealPoints or tuples; each point is an offset
2395 self
._originalPoints
= []
2398 self
._originalPoints
= the_points
2400 # Duplicate the list of points
2402 for point
in the_points
:
2403 new_point
= wx
.Point(point
[0], point
[1])
2404 self
._points
.append(new_point
)
2405 self
.CalculateBoundingBox()
2406 self
._originalWidth
= self
._boundWidth
2407 self
._originalHeight
= self
._boundHeight
2408 self
.SetDefaultRegionSize()
2410 def ClearPoints(self
):
2412 self
._originalPoints
= []
2414 # Width and height. Centre of object is centre of box
2415 def GetBoundingBoxMin(self
):
2416 return self
._boundWidth
, self
._boundHeight
2418 def GetPoints(self
):
2419 """Return the internal list of polygon vertices."""
2422 def GetOriginalPoints(self
):
2423 return self
._originalPoints
2425 def GetOriginalWidth(self
):
2426 return self
._originalWidth
2428 def GetOriginalHeight(self
):
2429 return self
._originalHeight
2431 def SetOriginalWidth(self
, w
):
2432 self
._originalWidth
= w
2434 def SetOriginalHeight(self
, h
):
2435 self
._originalHeight
= h
2437 def CalculateBoundingBox(self
):
2438 # Calculate bounding box at construction (and presumably resize) time
2444 for point
in self
._points
:
2452 if point
.y
> bottom
:
2455 self
._boundWidth
= right
- left
2456 self
._boundHeight
= bottom
- top
2458 def CalculatePolygonCentre(self
):
2459 """Recalculates the centre of the polygon, and
2460 readjusts the point offsets accordingly.
2461 Necessary since the centre of the polygon
2462 is expected to be the real centre of the bounding
2470 for point
in self
._points
:
2478 if point
.y
> bottom
:
2481 bwidth
= right
- left
2482 bheight
= bottom
- top
2484 newCentreX
= left
+ bwidth
/ 2.0
2485 newCentreY
= top
+ bheight
/ 2.0
2487 for point
in self
._points
:
2488 point
.x
-= newCentreX
2489 point
.y
-= newCentreY
2490 self
._xpos
+= newCentreX
2491 self
._ypos
+= newCentreY
2493 def HitTest(self
, x
, y
):
2494 # Imagine four lines radiating from this point. If all of these lines
2495 # hit the polygon, we're inside it, otherwise we're not. Obviously
2496 # we'd need more radiating lines to be sure of correct results for
2497 # very strange (concave) shapes.
2498 endPointsX
= [x
, x
+ 1000, x
, x
- 1000]
2499 endPointsY
= [y
- 1000, y
, y
+ 1000, y
]
2504 for point
in self
._points
:
2505 xpoints
.append(point
.x
+ self
._xpos
)
2506 ypoints
.append(point
.y
+ self
._ypos
)
2508 # We assume it's inside the polygon UNLESS one or more
2509 # lines don't hit the outline.
2513 if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]):
2519 nearest_attachment
= 0
2521 # If a hit, check the attachment points within the object
2524 for i
in range(self
.GetNumberOfAttachments()):
2525 e
= self
.GetAttachmentPositionEdge(i
)
2528 l
= math
.sqrt((xp
- x
) * (xp
- x
) + (yp
- y
) * (yp
- y
))
2531 nearest_attachment
= i
2533 return nearest_attachment
, nearest
2535 # Really need to be able to reset the shape! Otherwise, if the
2536 # points ever go to zero, we've lost it, and can't resize.
2537 def SetSize(self
, new_width
, new_height
, recursive
= True):
2538 self
.SetAttachmentSize(new_width
, new_height
)
2540 # Multiply all points by proportion of new size to old size
2541 x_proportion
= abs(float(new_width
) / self
._originalWidth
)
2542 y_proportion
= abs(float(new_height
) / self
._originalHeight
)
2544 for i
in range(max(len(self
._points
), len(self
._originalPoints
))):
2545 self
._points
[i
].x
= self
._originalPoints
[i
][0] * x_proportion
2546 self
._points
[i
].y
= self
._originalPoints
[i
][1] * y_proportion
2548 self
._boundWidth
= abs(new_width
)
2549 self
._boundHeight
= abs(new_height
)
2550 self
.SetDefaultRegionSize()
2552 # Make the original points the same as the working points
2553 def UpdateOriginalPoints(self
):
2554 """If we've changed the shape, must make the original points match the
2555 working points with this function.
2557 self
._originalPoints
= []
2559 for point
in self
._points
:
2560 original_point
= wx
.RealPoint(point
.x
, point
.y
)
2561 self
._originalPoints
.append(original_point
)
2563 self
.CalculateBoundingBox()
2564 self
._originalWidth
= self
._boundWidth
2565 self
._originalHeight
= self
._boundHeight
2567 def AddPolygonPoint(self
, pos
):
2568 """Add a control point after the given point."""
2570 firstPoint
= self
._points
[pos
]
2572 firstPoint
= self
._points
[0]
2575 secondPoint
= self
._points
[pos
+ 1]
2577 secondPoint
= self
._points
[0]
2579 x
= (secondPoint
.x
- firstPoint
.x
) / 2.0 + firstPoint
.x
2580 y
= (secondPoint
.y
- firstPoint
.y
) / 2.0 + firstPoint
.y
2581 point
= wx
.RealPoint(x
, y
)
2583 if pos
>= len(self
._points
) - 1:
2584 self
._points
.append(point
)
2586 self
._points
.insert(pos
+ 1, point
)
2588 self
.UpdateOriginalPoints()
2591 self
.DeleteControlPoints()
2592 self
.MakeControlPoints()
2594 def DeletePolygonPoint(self
, pos
):
2595 """Delete the given control point."""
2596 if pos
< len(self
._points
):
2597 del self
._points
[pos
]
2598 self
.UpdateOriginalPoints()
2600 self
.DeleteControlPoints()
2601 self
.MakeControlPoints()
2603 # Assume (x1, y1) is centre of box (most generally, line end at box)
2604 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2605 # First check for situation where the line is vertical,
2606 # and we would want to connect to a point on that vertical --
2607 # oglFindEndForPolyline can't cope with this (the arrow
2608 # gets drawn to the wrong place).
2609 if self
._attachmentMode
== ATTACHMENT_MODE_NONE
and x1
== x2
:
2610 # Look for the point we'd be connecting to. This is
2612 for point
in self
._points
:
2614 if y2
> y1
and point
.y
> 0:
2615 return point
.x
+ self
._xpos
, point
.y
+ self
._ypos
2616 elif y2
< y1
and point
.y
< 0:
2617 return point
.x
+ self
._xpos
, point
.y
+ self
._ypos
2621 for point
in self
._points
:
2622 xpoints
.append(point
.x
+ self
._xpos
)
2623 ypoints
.append(point
.y
+ self
._ypos
)
2625 return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
)
2627 def OnDraw(self
, dc
):
2628 if self
._shadowMode
!= SHADOW_NONE
:
2629 if self
._shadowBrush
:
2630 dc
.SetBrush(self
._shadowBrush
)
2631 dc
.SetPen(TransparentPen
)
2633 dc
.DrawPolygon(self
._points
, self
._xpos
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
)
2636 if self
._pen
.GetWidth() == 0:
2637 dc
.SetPen(TransparentPen
)
2639 dc
.SetPen(self
._pen
)
2641 dc
.SetBrush(self
._brush
)
2642 dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
)
2644 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
2645 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2646 # Multiply all points by proportion of new size to old size
2647 x_proportion
= abs(float(w
) / self
._originalWidth
)
2648 y_proportion
= abs(float(h
) / self
._originalHeight
)
2651 for point
in self
._originalPoints
:
2652 intPoints
.append(wx
.Point(x_proportion
* point
[0], y_proportion
* point
[1]))
2653 dc
.DrawPolygon(intPoints
, x
, y
)
2655 # Make as many control points as there are vertices
2656 def MakeControlPoints(self
):
2657 for point
in self
._points
:
2658 control
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
.x
, point
.y
)
2659 self
._canvas
.AddShape(control
)
2660 self
._controlPoints
.append(control
)
2662 def ResetControlPoints(self
):
2663 for i
in range(min(len(self
._points
), len(self
._controlPoints
))):
2664 point
= self
._points
[i
]
2665 self
._controlPoints
[i
]._xoffset
= point
.x
2666 self
._controlPoints
[i
]._yoffset
= point
.y
2667 self
._controlPoints
[i
].polygonVertex
= point
2669 def GetNumberOfAttachments(self
):
2670 maxN
= max(len(self
._points
) - 1, 0)
2671 for point
in self
._attachmentPoints
:
2672 if point
._id
> maxN
:
2676 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
2677 if self
._attachmentMode
== ATTACHMENT_MODE_EDGE
and self
._points
and attachment
< len(self
._points
):
2678 point
= self
._points
[0]
2679 return point
.x
+ self
._xpos
, point
.y
+ self
._ypos
2680 return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
)
2682 def AttachmentIsValid(self
, attachment
):
2683 if not self
._points
:
2686 if attachment
>= 0 and attachment
< len(self
._points
):
2689 for point
in self
._attachmentPoints
:
2690 if point
._id
== attachment
:
2695 # Rotate about the given axis by the given amount in radians
2696 def Rotate(self
, x
, y
, theta
):
2697 actualTheta
= theta
- self
._rotation
2699 # Rotate attachment points
2700 sinTheta
= math
.sin(actualTheta
)
2701 cosTheta
= math
.cos(actualTheta
)
2703 for point
in self
._attachmentPoints
:
2707 point
._x
= x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
2708 point
._y
= x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2710 for point
in self
._points
:
2714 point
.x
= x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
2715 point
.y
= x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2717 for point
in self
._originalPoints
:
2721 point
.x
= x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
2722 point
.y
= x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
2724 self
._rotation
= theta
2726 self
.CalculatePolygonCentre()
2727 self
.CalculateBoundingBox()
2728 self
.ResetControlPoints()
2730 # Control points ('handles') redirect control to the actual shape, to
2731 # make it easier to override sizing behaviour.
2732 def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys
= 0, attachment
= 0):
2733 dc
= wx
.ClientDC(self
.GetCanvas())
2734 self
.GetCanvas().PrepareDC(dc
)
2736 dc
.SetLogicalFunction(OGLRBLF
)
2738 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2739 dc
.SetPen(dottedPen
)
2740 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2742 # Code for CTRL-drag in C++ version commented out
2744 pt
.CalculateNewSize(x
, y
)
2746 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize().x
, pt
.GetNewSize().y
)
2748 def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2749 dc
= wx
.ClientDC(self
.GetCanvas())
2750 self
.GetCanvas().PrepareDC(dc
)
2754 dc
.SetLogicalFunction(OGLRBLF
)
2756 bound_x
, bound_y
= self
.GetBoundingBoxMin()
2758 dist
= math
.sqrt((x
- self
.GetX()) * (x
- self
.GetX()) + (y
- self
.GetY()) * (y
- self
.GetY()))
2760 pt
._originalDistance
= dist
2761 pt
._originalSize
.x
= bound_x
2762 pt
._originalSize
.y
= bound_y
2764 if pt
._originalDistance
== 0:
2765 pt
._originalDistance
= 0.0001
2767 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
2768 dc
.SetPen(dottedPen
)
2769 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2771 # Code for CTRL-drag in C++ version commented out
2773 pt
.CalculateNewSize(x
, y
)
2775 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize().x
, pt
.GetNewSize().y
)
2777 self
._canvas
.CaptureMouse()
2779 def OnSizingEndDragLeft(self
, pt
, x
, y
, keys
= 0, attachment
= 0):
2780 dc
= wx
.ClientDC(self
.GetCanvas())
2781 self
.GetCanvas().PrepareDC(dc
)
2783 if self
._canvas
.HasCapture():
2784 self
._canvas
.ReleaseMouse()
2785 dc
.SetLogicalFunction(wx
.COPY
)
2787 # If we're changing shape, must reset the original points
2789 self
.CalculateBoundingBox()
2790 self
.CalculatePolygonCentre()
2792 self
.SetSize(pt
.GetNewSize().x
, pt
.GetNewSize().y
)
2795 self
.ResetControlPoints()
2796 self
.Move(dc
, self
.GetX(), self
.GetY())
2797 if not self
._canvas
.GetQuickEditMode():
2798 self
._canvas
.Redraw(dc
)
2802 class EllipseShape(Shape
):
2803 """The EllipseShape behaves similarly to the RectangleShape but is
2809 def __init__(self
, w
, h
):
2810 Shape
.__init
__(self
)
2813 self
.SetDefaultRegionSize()
2815 def GetBoundingBoxMin(self
):
2816 return self
._width
, self
._height
2818 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2819 bound_x
, bound_y
= self
.GetBoundingBoxMax()
2821 return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
)
2826 def GetHeight(self
):
2829 def SetWidth(self
, w
):
2832 def SetHeight(self
, h
):
2835 def OnDraw(self
, dc
):
2836 if self
._shadowMode
!= SHADOW_NONE
:
2837 if self
._shadowBrush
:
2838 dc
.SetBrush(self
._shadowBrush
)
2839 dc
.SetPen(TransparentPen
)
2840 dc
.DrawEllipse(self
._xpos
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
,
2841 self
._ypos
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
,
2842 self
.GetWidth(), self
.GetHeight())
2845 if self
._pen
.GetWidth() == 0:
2846 dc
.SetPen(TransparentPen
)
2848 dc
.SetPen(self
._pen
)
2850 dc
.SetBrush(self
._brush
)
2851 dc
.DrawEllipse(self
._xpos
- self
.GetWidth() / 2.0, self
._ypos
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight())
2853 def SetSize(self
, x
, y
, recursive
= True):
2854 self
.SetAttachmentSize(x
, y
)
2857 self
.SetDefaultRegionSize()
2859 def GetNumberOfAttachments(self
):
2860 return Shape
.GetNumberOfAttachments(self
)
2862 # There are 4 attachment points on an ellipse - 0 = top, 1 = right,
2863 # 2 = bottom, 3 = left.
2864 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
2865 if self
._attachmentMode
== ATTACHMENT_MODE_BRANCHING
:
2866 return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
)
2868 if self
._attachmentMode
!= ATTACHMENT_MODE_NONE
:
2869 top
= self
._ypos
+ self
._height
/ 2.0
2870 bottom
= self
._ypos
- self
._height
/ 2.0
2871 left
= self
._xpos
- self
._width
/ 2.0
2872 right
= self
._xpos
+ self
._width
/ 2.0
2874 physicalAttachment
= self
.LogicalToPhysicalAttachment(attachment
)
2876 if physicalAttachment
== 0:
2877 if self
._spaceAttachments
:
2878 x
= left
+ (nth
+ 1) * self
._width
/ (no_arcs
+ 1.0)
2882 # We now have the point on the bounding box: but get the point
2883 # on the ellipse by imagining a vertical line from
2884 # (x, self._ypos - self_height - 500) to (x, self._ypos) intersecting
2887 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos
- self
._height
- 500, x
, self
._ypos
)
2888 elif physicalAttachment
== 1:
2890 if self
._spaceAttachments
:
2891 y
= bottom
+ (nth
+ 1) * self
._height
/ (no_arcs
+ 1.0)
2894 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos
+ self
._width
+ 500, y
, self
._xpos
, y
)
2895 elif physicalAttachment
== 2:
2896 if self
._spaceAttachments
:
2897 x
= left
+ (nth
+ 1) * self
._width
/ (no_arcs
+ 1.0)
2901 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos
+ self
._height
+ 500, x
, self
._ypos
)
2902 elif physicalAttachment
== 3:
2904 if self
._spaceAttachments
:
2905 y
= bottom
+ (nth
+ 1) * self
._height
/ (no_arcs
+ 1.0)
2908 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos
- self
._width
- 500, y
, self
._xpos
, y
)
2910 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
)
2912 return self
._xpos
, self
._ypos
2916 class CircleShape(EllipseShape
):
2917 """An EllipseShape whose width and height are the same."""
2918 def __init__(self
, diameter
):
2919 EllipseShape
.__init
__(self
, diameter
, diameter
)
2920 self
.SetMaintainAspectRatio(True)
2922 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
2923 return FindEndForCircle(self
._width
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
)
2927 class TextShape(RectangleShape
):
2928 """As wxRectangleShape, but only the text is displayed."""
2929 def __init__(self
, width
, height
):
2930 RectangleShape
.__init
__(self
, width
, height
)
2932 def OnDraw(self
, dc
):
2937 class ShapeRegion(object):
2938 """Object region."""
2939 def __init__(self
, region
= None):
2941 self
._regionText
= region
._regionText
2942 self
._regionName
= region
._regionName
2943 self
._textColour
= region
._textColour
2945 self
._font
= region
._font
2946 self
._minHeight
= region
._minHeight
2947 self
._minWidth
= region
._minWidth
2948 self
._width
= region
._width
2949 self
._height
= region
._height
2953 self
._regionProportionX
= region
._regionProportionX
2954 self
._regionProportionY
= region
._regionProportionY
2955 self
._formatMode
= region
._formatMode
2956 self
._actualColourObject
= region
._actualColourObject
2957 self
._actualPenObject
= None
2958 self
._penStyle
= region
._penStyle
2959 self
._penColour
= region
._penColour
2962 for line
in region
._formattedText
:
2963 new_line
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText())
2964 self
._formattedText
.append(new_line
)
2966 self
._regionText
= ""
2967 self
._font
= NormalFont
2968 self
._minHeight
= 5.0
2969 self
._minWidth
= 5.0
2975 self
._regionProportionX
= -1.0
2976 self
._regionProportionY
= -1.0
2977 self
._formatMode
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
2978 self
._regionName
= ""
2979 self
._textColour
= "BLACK"
2980 self
._penColour
= "BLACK"
2981 self
._penStyle
= wx
.SOLID
2982 self
._actualColourObject
= wx
.TheColourDatabase
.Find("BLACK")
2983 self
._actualPenObject
= None
2985 self
._formattedText
= []
2987 def ClearText(self
):
2988 self
._formattedText
= []
2990 def SetFont(self
, f
):
2993 def SetMinSize(self
, w
, h
):
2997 def SetSize(self
, w
, h
):
3001 def SetPosition(self
, xp
, yp
):
3005 def SetProportions(self
, xp
, yp
):
3006 self
._regionProportionX
= xp
3007 self
._regionProportionY
= yp
3009 def SetFormatMode(self
, mode
):
3010 self
._formatMode
= mode
3012 def SetColour(self
, col
):
3013 self
._textColour
= col
3014 self
._actualColourObject
= col
3016 def GetActualColourObject(self
):
3017 self
._actualColourObject
= wx
.TheColourDatabase
.Find(self
.GetColour())
3018 return self
._actualColourObject
3020 def SetPenColour(self
, col
):
3021 self
._penColour
= col
3022 self
._actualPenObject
= None
3024 # Returns NULL if the pen is invisible
3025 # (different to pen being transparent; indicates that
3026 # region boundary should not be drawn.)
3027 def GetActualPen(self
):
3028 if self
._actualPenObject
:
3029 return self
._actualPenObject
3031 if not self
._penColour
:
3033 if self
._penColour
=="Invisible":
3035 self
._actualPenObject
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
)
3036 return self
._actualPenObject
3038 def SetText(self
, s
):
3039 self
._regionText
= s
3041 def SetName(self
, s
):
3042 self
._regionName
= s
3045 return self
._regionText
3050 def GetMinSize(self
):
3051 return self
._minWidth
, self
._minHeight
3053 def GetProportion(self
):
3054 return self
._regionProportionX
, self
._regionProportionY
3057 return self
._width
, self
._height
3059 def GetPosition(self
):
3060 return self
._x
, self
._y
3062 def GetFormatMode(self
):
3063 return self
._formatMode
3066 return self
._regionName
3068 def GetColour(self
):
3069 return self
._textColour
3071 def GetFormattedText(self
):
3072 return self
._formattedText
3074 def GetPenColour(self
):
3075 return self
._penColour
3077 def GetPenStyle(self
):
3078 return self
._penStyle
3080 def SetPenStyle(self
, style
):
3081 self
._penStyle
= style
3082 self
._actualPenObject
= None
3087 def GetHeight(self
):
3092 class ControlPoint(RectangleShape
):
3093 def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
):
3094 RectangleShape
.__init
__(self
, size
, size
)
3096 self
._canvas
= theCanvas
3097 self
._shape
= object
3098 self
._xoffset
= the_xoffset
3099 self
._yoffset
= the_yoffset
3100 self
._type
= the_type
3101 self
.SetPen(BlackForegroundPen
)
3102 self
.SetBrush(wx
.BLACK_BRUSH
)
3103 self
._oldCursor
= None
3104 self
._visible
= True
3105 self
._eraseObject
= True
3107 # Don't even attempt to draw any text - waste of time
3108 def OnDrawContents(self
, dc
):
3111 def OnDraw(self
, dc
):
3112 self
._xpos
= self
._shape
.GetX() + self
._xoffset
3113 self
._ypos
= self
._shape
.GetY() + self
._yoffset
3114 RectangleShape
.OnDraw(self
, dc
)
3116 def OnErase(self
, dc
):
3117 RectangleShape
.OnErase(self
, dc
)
3119 # Implement resizing of canvas object
3120 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
3121 self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
)
3123 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3124 self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
)
3126 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3127 self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
)
3129 def GetNumberOfAttachments(self
):
3132 def GetAttachmentPosition(self
, attachment
, nth
= 0, no_arcs
= 1, line
= None):
3133 return self
._xpos
, self
._ypos
3135 def SetEraseObject(self
, er
):
3136 self
._eraseObject
= er
3139 class PolygonControlPoint(ControlPoint
):
3140 def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
):
3141 ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0)
3142 self
._polygonVertex
= vertex
3143 self
._originalDistance
= 0.0
3144 self
._newSize
= wx
.RealPoint()
3145 self
._originalSize
= wx
.RealPoint()
3147 def GetNewSize(self
):
3148 return self
._newSize
3150 # Calculate what new size would be, at end of resize
3151 def CalculateNewSize(self
, x
, y
):
3152 bound_x
, bound_y
= self
.GetShape().GetBoundingBoxMax()
3153 dist
= math
.sqrt((x
- self
._shape
.GetX()) * (x
- self
._shape
.GetX()) + (y
- self
._shape
.GetY()) * (y
- self
._shape
.GetY()))
3155 self
._newSize
.x
= dist
/ self
._originalDistance
* self
._originalSize
.x
3156 self
._newSize
.y
= dist
/ self
._originalDistance
* self
._originalSize
.y
3158 # Implement resizing polygon or moving the vertex
3159 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
3160 self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
)
3162 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3163 self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
)
3165 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
3166 self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
)
3168 from _canvas
import *
3169 from _lines
import *
3170 from _composit
import *