1# -*- coding: utf-8 -*-
2#----------------------------------------------------------------------------
3# Name:         basic.py
4# Purpose:      The basic OGL shapes
5#
6# Author:       Pierre Hjälm (from C++ original by Julian Smart)
7#
8# Created:      2004-05-08
9# Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
10# Licence:      wxWindows license
11# Tags:         phoenix-port, unittest, py3-port, documented
12#----------------------------------------------------------------------------
13"""
14The basic shapes for OGL
15"""
16
17import wx
18import math
19
20from .oglmisc import *
21
22DragOffsetX = 0.0
23DragOffsetY = 0.0
24
25
26def OGLInitialize():
27    """Initialize OGL.
28
29    :note: This creates some pens and brushes that the OGL library uses.
30     It should be called after the app object has been created, but
31     before OGL is used.
32    """
33    global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen
34    global BlackForegroundPen, NormalFont
35
36    WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_SOLID)
37    WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID)
38
39    TransparentPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_TRANSPARENT)
40    BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_SOLID)
41
42    NormalFont = wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
43
44
45def OGLCleanUp():
46    """not implemented???"""
47    pass
48
49
50class ShapeTextLine(object):
51    """The :class:`ShapeTextLine` class."""
52    def __init__(self, the_x, the_y, the_line):
53        """
54        Default class constructor.
55
56        :param int `the_x`: the x position
57        :param int `the_y`: the y position
58        :param str `the_line`: the text
59
60        """
61        self._x = the_x
62        self._y = the_y
63        self._line = the_line
64
65    def GetX(self):
66        """Get the x position."""
67        return self._x
68
69    def GetY(self):
70        """Get the y position."""
71        return self._y
72
73    def SetX(self, x):
74        """
75        Set the x position.
76
77        :param int `x`: the x position
78
79        """
80        self._x = x
81
82    def SetY(self, y):
83        """
84        Set the y position.
85
86        :param int `y`: the x position
87
88        """
89        self._y = y
90
91    def SetText(self, text):
92        """
93        Set the text.
94
95        :param str `text`: the text
96
97        """
98        self._line = text
99
100    def GetText(self):
101        """Get the text."""
102        return self._line
103
104
105class ShapeEvtHandler(object):
106    """The :class:`ShapeEvtHandler` class."""
107    def __init__(self, prev = None, shape = None):
108        """
109        Default class constructor.
110
111        :param `pref`: the previous event handler, an instance of
112         :class:`ShapeEvtHandler` ???
113        :param `shape`: the shape, an instance of :class:`Shape`
114
115        """
116        self._previousHandler = prev
117        self._handlerShape = shape
118
119    def SetShape(self, sh):
120        """
121        Set associated shape
122
123        :param `sh`:  the shape, an instance of :class:`Shape`
124
125        """
126        self._handlerShape = sh
127
128    def GetShape(self):
129        """Get associated shape."""
130        return self._handlerShape
131
132    def SetPreviousHandler(self, handler):
133        """
134        Set previous event handler.
135
136        :param `handler`: the previous handler, an instance of
137         :class:`ShapeEvtHandler` ???
138
139        """
140        self._previousHandler = handler
141
142    def GetPreviousHandler(self):
143        """Get previous event handler."""
144        return self._previousHandler
145
146    def OnDelete(self):
147        """The delete handler."""
148        if self!=self.GetShape():
149            del self
150
151    def OnDraw(self, dc):
152        """The draw handler."""
153        if self._previousHandler:
154            self._previousHandler.OnDraw(dc)
155
156    def OnMoveLinks(self, dc):
157        """The move links handler."""
158        if self._previousHandler:
159            self._previousHandler.OnMoveLinks(dc)
160
161    def OnMoveLink(self, dc, moveControlPoints = True):
162        """The move link handler."""
163        if self._previousHandler:
164            self._previousHandler.OnMoveLink(dc, moveControlPoints)
165
166    def OnDrawContents(self, dc):
167        """The draw contents handler."""
168        if self._previousHandler:
169            self._previousHandler.OnDrawContents(dc)
170
171    def OnDrawBranches(self, dc, erase = False):
172        """The draw branches handler."""
173        if self._previousHandler:
174            self._previousHandler.OnDrawBranches(dc, erase = erase)
175
176    def OnSize(self, x, y):
177        """The size handler."""
178        if self._previousHandler:
179            self._previousHandler.OnSize(x, y)
180
181    def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
182        """The pre move handler."""
183        if self._previousHandler:
184            return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display)
185        else:
186            return True
187
188    def OnMovePost(self, dc, x, y, old_x, old_y, display = True):
189        """The post move handler."""
190        if self._previousHandler:
191            return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display)
192        else:
193            return True
194
195    def OnErase(self, dc):
196        """The erase handler."""
197        if self._previousHandler:
198            self._previousHandler.OnErase(dc)
199
200    def OnEraseContents(self, dc):
201        """The erase contents handler."""
202        if self._previousHandler:
203            self._previousHandler.OnEraseContents(dc)
204
205    def OnHighlight(self, dc):
206        """The highlight handler."""
207        if self._previousHandler:
208            self._previousHandler.OnHighlight(dc)
209
210    def OnLeftClick(self, x, y, keys, attachment):
211        """The left click handler."""
212        if self._previousHandler:
213            self._previousHandler.OnLeftClick(x, y, keys, attachment)
214
215    def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
216        """The left double click handler."""
217        if self._previousHandler:
218            self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment)
219
220    def OnRightClick(self, x, y, keys = 0, attachment = 0):
221        """The right click handler."""
222        if self._previousHandler:
223            self._previousHandler.OnRightClick(x, y, keys, attachment)
224
225    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
226        """The drag left handler."""
227        if self._previousHandler:
228            self._previousHandler.OnDragLeft(draw, x, y, keys, attachment)
229
230    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
231        """The begin drag left handler."""
232        if self._previousHandler:
233            self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
234
235    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
236        """The end drag left handler."""
237        if self._previousHandler:
238            self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
239
240    def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
241        """The drag right handler."""
242        if self._previousHandler:
243            self._previousHandler.OnDragRight(draw, x, y, keys, attachment)
244
245    def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
246        """The begin drag right handler."""
247        if self._previousHandler:
248            self._previousHandler.OnBeginDragRight(x, y, keys, attachment)
249
250    def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
251        """The end drag right handler."""
252        if self._previousHandler:
253            self._previousHandler.OnEndDragRight(x, y, keys, attachment)
254
255    # Control points ('handles') redirect control to the actual shape,
256    # to make it easier to override sizing behaviour.
257    def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
258        """The sizing drag left handler."""
259        if self._previousHandler:
260            self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment)
261
262    def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
263        """The sizing begin drag left handler."""
264        if self._previousHandler:
265            self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment)
266
267    def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
268        """The sizing end drag left handler."""
269        if self._previousHandler:
270            self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment)
271
272    def OnBeginSize(self, w, h):
273        """not implemented???"""
274        pass
275
276    def OnEndSize(self, w, h):
277        """not implemented???"""
278        pass
279
280    def OnDrawOutline(self, dc, x, y, w, h):
281        """The drag outline handler."""
282        if self._previousHandler:
283            self._previousHandler.OnDrawOutline(dc, x, y, w, h)
284
285    def OnDrawControlPoints(self, dc):
286        """The draw control points handler."""
287        if self._previousHandler:
288            self._previousHandler.OnDrawControlPoints(dc)
289
290    def OnEraseControlPoints(self, dc):
291        """The erase control points handler."""
292        if self._previousHandler:
293            self._previousHandler.OnEraseControlPoints(dc)
294
295    # Can override this to prevent or intercept line reordering.
296    def OnChangeAttachment(self, attachment, line, ordering):
297        """The change attachment handler."""
298        if self._previousHandler:
299            self._previousHandler.OnChangeAttachment(attachment, line, ordering)
300
301
302class Shape(ShapeEvtHandler):
303    """
304    The :class:`Shape` is the base class for OGL shapes.
305
306    The :class:`Shape` is the top-level, abstract object that all other objects
307    are derived from. All common functionality is represented by :class:`Shape`
308    members, and overriden members that appear in derived classes and have
309    behaviour as documented for :class:`Shape`, are not documented separately.
310    """
311    GraphicsInSizeToContents = False
312
313    def __init__(self, canvas = None):
314        """
315        Default class constructor.
316
317        :param `canvas`: an instance of :class:`~lib.ogl.Canvas`
318
319        """
320        ShapeEvtHandler.__init__(self)
321
322        self._eventHandler = self
323        self.SetShape(self)
324        self._id = 0
325        self._formatted = False
326        self._canvas = canvas
327        self._xpos = 0.0
328        self._ypos = 0.0
329        self._pen = BlackForegroundPen
330        self._brush = wx.WHITE_BRUSH
331        self._font = NormalFont
332        self._textColour = wx.BLACK
333        self._textColourName = wx.BLACK
334        self._visible = False
335        self._selected = False
336        self._attachmentMode = ATTACHMENT_MODE_NONE
337        self._spaceAttachments = True
338        self._disableLabel = False
339        self._fixedWidth = False
340        self._fixedHeight = False
341        self._drawHandles = True
342        self._sensitivity = OP_ALL
343        self._draggable = True
344        self._parent = None
345        self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
346        self._shadowMode = SHADOW_NONE
347        self._shadowOffsetX = 6
348        self._shadowOffsetY = 6
349        self._shadowBrush = wx.BLACK_BRUSH
350        self._textMarginX = 5
351        self._textMarginY = 5
352        self._regionName = "0"
353        self._centreResize = True
354        self._maintainAspectRatio = False
355        self._highlighted = False
356        self._rotation = 0.0
357        self._branchNeckLength = 10
358        self._branchStemLength = 10
359        self._branchSpacing = 10
360        self._branchStyle = BRANCHING_ATTACHMENT_NORMAL
361
362        self._regions = []
363        self._lines = []
364        self._controlPoints = []
365        self._attachmentPoints = []
366        self._text = []
367        self._children = []
368
369        # Set up a default region. Much of the above will be put into
370        # the region eventually (the duplication is for compatibility)
371        region = ShapeRegion()
372        region.SetName("0")
373        region.SetFont(NormalFont)
374        region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT)
375        region.SetColour("BLACK")
376        self._regions.append(region)
377
378    def __str__(self):
379        return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
380
381    def GetClassName(self):
382        return str(self.__class__).split(".")[-1][:-2]
383
384    def Delete(self):
385        """
386        Fully disconnect this shape from parents, children, the
387        canvas, etc.
388        """
389        if self._parent:
390            self._parent.GetChildren().remove(self)
391
392        for child in self.GetChildren():
393            child.Delete()
394
395        self.ClearText()
396        self.ClearRegions()
397        self.ClearAttachments()
398
399        self._handlerShape = None
400
401        if self._canvas:
402            self.RemoveFromCanvas(self._canvas)
403
404        if self.GetEventHandler():
405            self.GetEventHandler().OnDelete()
406        self._eventHandler = None
407
408    def Draggable(self):
409        """
410        Is shape draggable?
411
412        :returns: `True` if the shape may be dragged by the user.
413        """
414        return True
415
416    def SetShape(self, sh):
417        """Set shape ???
418
419        :param `sh`: an instance of :class:`Shape`
420
421        """
422        self._handlerShape = sh
423
424    def GetCanvas(self):
425        """Get the internal canvas."""
426        return self._canvas
427
428    def GetBranchStyle(self):
429        """Get the branch style."""
430        return self._branchStyle
431
432    def GetRotation(self):
433        """Return the angle of rotation in radians."""
434        return self._rotation
435
436    def SetRotation(self, rotation):
437        """Set rotation
438
439        :param int `rotation`: rotation
440
441        """
442        self._rotation = rotation
443
444    def SetHighlight(self, hi, recurse = False):
445        """Set the highlight for a shape. Shape highlighting is unimplemented."""
446        self._highlighted = hi
447        if recurse:
448            for shape in self._children:
449                shape.SetHighlight(hi, recurse)
450
451    def SetSensitivityFilter(self, sens = OP_ALL, recursive = False):
452        """
453        Set the shape to be sensitive or insensitive to specific mouse
454        operations.
455
456        :param `sens`: is a bitlist of the following:
457
458        ========================== ===================
459        Mouse operation            Description
460        ========================== ===================
461        `OP_CLICK_LEFT`            left clicked
462        `OP_CLICK_RIGHT`           right clicked
463        `OP_DRAG_LEFT`             left drag
464        `OP_DRAG_RIGHT`            right drag
465        `OP_ALL`                   all of the above
466        ========================== ===================
467
468        :param `recursive`: if `True` recurse through children
469
470        """
471        self._draggable = sens & OP_DRAG_LEFT
472
473        self._sensitivity = sens
474        if recursive:
475            for shape in self._children:
476                shape.SetSensitivityFilter(sens, True)
477
478    def SetDraggable(self, drag, recursive = False):
479        """Set the shape to be draggable or not draggable.
480
481        :param `drag`: if `True` make shape draggable
482        :param `recursive`: if `True` recurse through children
483
484        """
485        self._draggable = drag
486        if drag:
487            self._sensitivity |= OP_DRAG_LEFT
488        elif self._sensitivity & OP_DRAG_LEFT:
489            self._sensitivity -= OP_DRAG_LEFT
490
491        if recursive:
492            for shape in self._children:
493                shape.SetDraggable(drag, True)
494
495    def SetDrawHandles(self, drawH):
496        """
497        Set the drawHandles flag for this shape and all descendants.
498
499        :param `drawH`: if `True` (the default), any handles (control points)
500         will be drawn. Otherwise, the handles will not be drawn.
501
502        """
503        self._drawHandles = drawH
504        for shape in self._children:
505            shape.SetDrawHandles(drawH)
506
507    def SetShadowMode(self, mode, redraw = False):
508        """
509        Set the shadow mode (whether a shadow is drawn or not).
510
511        :param `mode`: can be one of the following:
512
513        =============================== ===========================
514        Shadow mode                     Description
515        =============================== ===========================
516        `SHADOW_NONE`                   No shadow (the default)
517        `SHADOW_LEFT`                   Shadow on the left side
518        `SHADOW_RIGHT`                  Shadow on the right side
519        =============================== ===========================
520
521        """
522        if redraw and self.GetCanvas():
523            dc = wx.MemoryDC()
524            dc.SelectObject(self.GetCanvas().GetBuffer())
525            self.GetCanvas().PrepareDC(dc)
526            self.Erase(dc)
527            self._shadowMode = mode
528            self.Draw(dc)
529        else:
530            self._shadowMode = mode
531
532    def GetShadowMode(self):
533        """Get the current shadow mode setting."""
534        return self._shadowMode
535
536    def SetCanvas(self, theCanvas):
537        """
538        Set the canvas, identical to Shape.Attach.
539
540        :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
541
542        """
543        self._canvas = theCanvas
544        for shape in self._children:
545            shape.SetCanvas(theCanvas)
546
547    def AddToCanvas(self, theCanvas, addAfter = None):
548        """
549        Add the shape to the canvas's shape list.
550
551        :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
552        :param `addAfter`: if non-NULL, will add the shape after this shape
553
554        """
555        theCanvas.AddShape(self, addAfter)
556
557        lastImage = self
558        for object in self._children:
559            object.AddToCanvas(theCanvas, lastImage)
560            lastImage = object
561
562    def InsertInCanvas(self, theCanvas):
563        """
564        Insert the shape at the front of the shape list of canvas.
565
566        :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
567
568        """
569        theCanvas.InsertShape(self)
570
571        lastImage = self
572        for object in self._children:
573            object.AddToCanvas(theCanvas, lastImage)
574            lastImage = object
575
576    def RemoveFromCanvas(self, theCanvas):
577        """
578        Remove the shape from the canvas.
579
580        :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
581
582        """
583        if self.Selected():
584            self.Select(False)
585
586        self._canvas = None
587        theCanvas.RemoveShape(self)
588        for object in self._children:
589            object.RemoveFromCanvas(theCanvas)
590
591    def ClearAttachments(self):
592        """Clear internal custom attachment point shapes (of class
593        :class:`~lib.ogl.AttachmentPoint`)
594        """
595        self._attachmentPoints = []
596
597    def ClearText(self, regionId = 0):
598        """
599        Clear the text from the specified text region.
600
601        :param `regionId`: the region identifier
602
603        """
604        if regionId == 0:
605            self._text = ""
606        if regionId < len(self._regions):
607            self._regions[regionId].ClearText()
608
609    def ClearRegions(self):
610        """Clear the ShapeRegions from the shape."""
611        self._regions = []
612
613    def AddRegion(self, region):
614        """Add a region to the shape."""
615        self._regions.append(region)
616
617    def SetDefaultRegionSize(self):
618        """Set the default region to be consistent with the shape size."""
619        if not self._regions:
620            return
621        w, h = self.GetBoundingBoxMax()
622        self._regions[0].SetSize(w, h)
623
624    def HitTest(self, x, y):
625        """
626        Given a point on a canvas, returns `True` if the point was on the
627        shape, and returns the nearest attachment point and distance from
628        the given point and target.
629
630        :param `x`: the x position
631        :param `y`: the y position
632
633        """
634        width, height = self.GetBoundingBoxMax()
635        if abs(width) < 4:
636            width = 4.0
637        if abs(height) < 4:
638            height = 4.0
639
640        width += 4 # Allowance for inaccurate mousing
641        height += 4
642
643        left = self._xpos - width / 2.0
644        top = self._ypos - height / 2.0
645        right = self._xpos + width / 2.0
646        bottom = self._ypos + height / 2.0
647
648        nearest_attachment = 0
649
650        # If within the bounding box, check the attachment points
651        # within the object.
652        if x >= left and x <= right and y >= top and y <= bottom:
653            n = self.GetNumberOfAttachments()
654            nearest = 999999
655
656            # GetAttachmentPosition[Edge] takes a logical attachment position,
657            # i.e. if it's rotated through 90%, position 0 is East-facing.
658
659            for i in range(n):
660                e = self.GetAttachmentPositionEdge(i)
661                if e:
662                    xp, yp = e
663                    l = math.sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y))
664                    if l < nearest:
665                        nearest = l
666                        nearest_attachment = i
667
668            return nearest_attachment, nearest
669        return False
670
671    # Format a text string according to the region size, adding
672    # strings with positions to region text list
673
674    def FormatText(self, dc, s, i = 0):
675        """
676        Reformat the given text region; defaults to formatting the
677        default region.
678
679        :param `dc`: the device contexr
680        :param str `s`: the text string
681        :param int `i`: the region identifier
682
683        """
684        self.ClearText(i)
685
686        if not self._regions:
687            return
688
689        if i >= len(self._regions):
690            return
691
692        region = self._regions[i]
693        region._regionText = s
694        dc.SetFont(region.GetFont())
695
696        w, h = region.GetSize()
697
698        stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode())
699        for s in stringList:
700            line = ShapeTextLine(0.0, 0.0, s)
701            region.GetFormattedText().append(line)
702
703        actualW = w
704        actualH = h
705        # Don't try to resize an object with more than one image (this
706        # case should be dealt with by overriden handlers)
707        if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \
708           len(region.GetFormattedText()) and \
709           len(self._regions) == 1 and \
710           not Shape.GraphicsInSizeToContents:
711
712            actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText())
713            if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h:
714                # If we are a descendant of a composite, must make sure
715                # the composite gets resized properly
716
717                topAncestor = self.GetTopAncestor()
718                if topAncestor != self:
719                    Shape.GraphicsInSizeToContents = True
720
721                    composite = topAncestor
722                    composite.Erase(dc)
723                    self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
724                    self.Move(dc, self._xpos, self._ypos)
725                    composite.CalculateSize()
726                    if composite.Selected():
727                        composite.DeleteControlPoints(dc)
728                        composite.MakeControlPoints()
729                        composite.MakeMandatoryControlPoints()
730                    # Where infinite recursion might happen if we didn't stop it
731                    composite.Draw(dc)
732                    Shape.GraphicsInSizeToContents = False
733                else:
734                    self.Erase(dc)
735
736                self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
737                self.Move(dc, self._xpos, self._ypos)
738                self.EraseContents(dc)
739        CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode())
740        self._formatted = True
741
742    def Recentre(self, dc):
743        """
744        Recentre (or other formatting) all the text regions for this shape.
745        """
746        w, h = self.GetBoundingBoxMin()
747        for region in self._regions:
748            CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode())
749
750    def GetPerimeterPoint(self, x1, y1, x2, y2):
751        """
752        Get the point at which the line from (x1, y1) to (x2, y2) hits
753        the shape.
754
755        :param `x1`: the x1 position
756        :param `y1`: the y1 position
757        :param `x2`: the x2 position
758        :param `y2`: the y2 position
759
760        :returns: `False` if the line doesn't hit the perimeter.
761
762        """
763        return False
764
765    def SetPen(self, the_pen):
766        """Set the pen for drawing the shape's outline."""
767        self._pen = the_pen
768
769    def SetBrush(self, the_brush):
770        """Set the brush for filling the shape's shape."""
771        self._brush = the_brush
772
773    # Get the top - most (non-division) ancestor, or self
774    def GetTopAncestor(self):
775        """
776        Return the top-most ancestor of this shape (the root of
777        the composite).
778        """
779        if not self.GetParent():
780            return self
781
782        if isinstance(self.GetParent(), DivisionShape):
783            return self
784        return self.GetParent().GetTopAncestor()
785
786    # Region functions
787    def SetFont(self, the_font, regionId = 0):
788        """
789        Set the font for the specified text region.
790
791        :param `the_font`: an instance of :class:`wx.Font` ???
792        :param `regionId`: the region identifier
793
794        """
795        self._font = the_font
796        if regionId < len(self._regions):
797            self._regions[regionId].SetFont(the_font)
798
799    def GetFont(self, regionId = 0):
800        """
801        Get the font for the specified text region.
802
803        :param `regionId`: the region identifier
804
805        """
806        if regionId >= len(self._regions):
807            return None
808        return self._regions[regionId].GetFont()
809
810    def SetFormatMode(self, mode, regionId = 0):
811        """
812        Set the format mode of the region.
813
814        :param `mode`: can be a bit list of the following
815
816        ============================== ==============================
817        Format mode                    Description
818        ============================== ==============================
819        `FORMAT_NONE`                  No formatting
820        `FORMAT_CENTRE_HORIZ`          Horizontal centring
821        `FORMAT_CENTRE_VERT`           Vertical centring
822        ============================== ==============================
823
824        :param `regionId`: the region identifier, default=0
825
826        """
827        if regionId < len(self._regions):
828            self._regions[regionId].SetFormatMode(mode)
829
830    def GetFormatMode(self, regionId = 0):
831        """
832        Get the format mode.
833
834        :param `regionId`: the region identifier, default=0
835
836        """
837        if regionId >= len(self._regions):
838            return 0
839        return self._regions[regionId].GetFormatMode()
840
841    def SetTextColour(self, the_colour, regionId = 0):
842        """
843        Set the colour for the specified text region.
844
845        :param str `the_colour`: a valid colour name,
846         see :class:`wx.ColourDatabase`
847        :param `regionId`: the region identifier
848
849        """
850        self._textColour = wx.TheColourDatabase.Find(the_colour)
851        self._textColourName = the_colour
852
853        if regionId < len(self._regions):
854            self._regions[regionId].SetColour(the_colour)
855
856    def GetTextColour(self, regionId = 0):
857        """
858        Get the colour for the specified text region.
859
860        :param `regionId`: the region identifier
861
862        """
863        if regionId >= len(self._regions):
864            return ""
865        return self._regions[regionId].GetColour()
866
867    def SetRegionName(self, name, regionId = 0):
868        """
869        Set the name for this region.
870
871        :param str `name`: the name to set
872        :param `regionId`: the region identifier
873
874        :note: The name for a region is unique within the scope of the whole
875         composite, whereas a region id is unique only for a single image.
876
877        """
878        if regionId < len(self._regions):
879            self._regions[regionId].SetName(name)
880
881    def GetRegionName(self, regionId = 0):
882        """
883        Get the region's name.
884
885        :param `regionId`: the region identifier
886
887        :note: A region's name can be used to uniquely determine a region within
888         an entire composite image hierarchy. See also
889         :meth:`~lib.ogl.Shape.SetRegionName`.
890
891        """
892        if regionId >= len(self._regions):
893            return ""
894        return self._regions[regionId].GetName()
895
896    def GetRegionId(self, name):
897        """
898        Get the region's identifier by name.
899
900        :param str `name`: the regions name
901
902        :note: This is not unique for within an entire composite, but is unique
903         for the image.
904
905        """
906        for i, r in enumerate(self._regions):
907            if r.GetName() == name:
908                return i
909        return -1
910
911    # Name all _regions in all subimages recursively
912    def NameRegions(self, parentName=""):
913        """
914        Make unique names for all the regions in a shape or composite shape.
915
916        :param str `parentName`: a prefix for the region names
917
918        """
919        n = self.GetNumberOfTextRegions()
920        for i in range(n):
921            if parentName:
922                buff = parentName+"."+str(i)
923            else:
924                buff = str(i)
925            self.SetRegionName(buff, i)
926
927        for j, child in enumerate(self._children):
928            if parentName:
929                buff = parentName+"."+str(j)
930            else:
931                buff = str(j)
932            child.NameRegions(buff)
933
934    # Get a region by name, possibly looking recursively into composites
935    def FindRegion(self, name):
936        """
937        Find the actual image ('this' if non-composite) and region id
938        for the given region name.
939
940        :param str `name`: the region name
941
942        """
943        id = self.GetRegionId(name)
944        if id > -1:
945            return self, id
946
947        for child in self._children:
948            actualImage, regionId = child.FindRegion(name)
949            if actualImage:
950                return actualImage, regionId
951
952        return None, -1
953
954    # Finds all region names for this image (composite or simple).
955    def FindRegionNames(self):
956        """Get a list of all region names for this image (composite or simple)."""
957        list = []
958        n = self.GetNumberOfTextRegions()
959        for i in range(n):
960            list.append(self.GetRegionName(i))
961
962        for child in self._children:
963            list += child.FindRegionNames()
964
965        return list
966
967    def AssignNewIds(self):
968        """Assign new ids to this image and its children."""
969        self._id = wx.NewIdRef()
970        for child in self._children:
971            child.AssignNewIds()
972
973    def OnDraw(self, dc):
974        """not implemented???"""
975        pass
976
977    def OnMoveLinks(self, dc):
978        """The move links handler."""
979        # Want to set the ends of all attached links
980        # to point to / from this object
981
982        for line in self._lines:
983            line.GetEventHandler().OnMoveLink(dc)
984
985    def OnDrawContents(self, dc):
986        """The draw contents handler."""
987        if not self._regions:
988            return
989
990        bound_x, bound_y = self.GetBoundingBoxMin()
991
992        if self._pen:
993            dc.SetPen(self._pen)
994
995        for region in self._regions:
996            if region.GetFont():
997                dc.SetFont(region.GetFont())
998
999            dc.SetTextForeground(region.GetActualColourObject())
1000            dc.SetBackgroundMode(wx.TRANSPARENT)
1001            if not self._formatted:
1002                CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
1003                self._formatted = True
1004
1005            if not self.GetDisableLabel():
1006                DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
1007
1008    def DrawContents(self, dc):
1009        """
1010        Draw the internal graphic of the shape (such as text).
1011
1012        Do not override this function: override OnDrawContents, which
1013        is called by this function.
1014        """
1015        self.GetEventHandler().OnDrawContents(dc)
1016
1017    def OnSize(self, x, y):
1018        """not implemented???"""
1019        pass
1020
1021    def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
1022        return True
1023
1024    def OnErase(self, dc):
1025        """The erase handler."""
1026        if not self._visible:
1027            return
1028
1029        # Erase links
1030        for line in self._lines:
1031            line.GetEventHandler().OnErase(dc)
1032
1033        self.GetEventHandler().OnEraseContents(dc)
1034
1035    def OnEraseContents(self, dc):
1036        """The erase contents handler."""
1037        if not self._visible:
1038            return
1039
1040        xp, yp = self.GetX(), self.GetY()
1041        minX, minY = self.GetBoundingBoxMin()
1042        maxX, maxY = self.GetBoundingBoxMax()
1043
1044        topLeftX = xp - maxX / 2.0 - 2
1045        topLeftY = yp - maxY / 2.0 - 2
1046
1047        penWidth = 0
1048        if self._pen:
1049            penWidth = self._pen.GetWidth()
1050
1051        dc.SetPen(self.GetBackgroundPen())
1052        dc.SetBrush(self.GetBackgroundBrush())
1053
1054        dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4)
1055
1056    def EraseLinks(self, dc, attachment = -1, recurse = False):
1057        """
1058        Erase links attached to this shape, but do not repair damage
1059        caused to other shapes.
1060
1061        :param `dc`: the device context
1062        :param `attachment`: ???
1063        :param `recurse`: if `True` recurse through the children
1064
1065        """
1066        if not self._visible:
1067            return
1068
1069        for line in self._lines:
1070            if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
1071                line.GetEventHandler().OnErase(dc)
1072
1073        if recurse:
1074            for child in self._children:
1075                child.EraseLinks(dc, attachment, recurse)
1076
1077    def DrawLinks(self, dc, attachment = -1, recurse = False):
1078        """
1079        Draws any lines linked to this shape.
1080
1081        :param `dc`: the device context
1082        :param `attachment`: ???
1083        :param `recurse`: if `True` recurse through the children
1084
1085        """
1086        if not self._visible:
1087            return
1088
1089        for line in self._lines:
1090            if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
1091                line.Draw(dc)
1092
1093        if recurse:
1094            for child in self._children:
1095                child.DrawLinks(dc, attachment, recurse)
1096
1097    #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before
1098    #  another on an edge of the shape.
1099    # attachmentPoint is the attachment point (= side) in question.
1100
1101    # This is the default, rectangular implementation.
1102    def AttachmentSortTest(self, attachmentPoint, pt1, pt2):
1103        """
1104        Return TRUE if pt1 is less than or equal to pt2, in the sense
1105        that one point comes before another on an edge of the shape.
1106
1107        attachment is the attachment point (side) in question.
1108
1109        This function is used in Shape.MoveLineToNewAttachment to determine
1110        the new line ordering.
1111        """
1112        physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint)
1113        if physicalAttachment in [0, 2]:
1114            return pt1[0] <= pt2[0]
1115        elif physicalAttachment in [1, 3]:
1116            return pt1[1] <= pt2[1]
1117
1118        return False
1119
1120    def MoveLineToNewAttachment(self, dc, to_move, x, y):
1121        """
1122        Move the given line (which must already be attached to the shape)
1123        to a different attachment point on the shape, or a different order
1124        on the same attachment.
1125
1126        Calls Shape.AttachmentSortTest and then
1127        ShapeEvtHandler.OnChangeAttachment.
1128        """
1129        if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
1130            return False
1131
1132        # Is (x, y) on this object? If so, find the new attachment point
1133        # the user has moved the point to
1134        hit = self.HitTest(x, y)
1135        if not hit:
1136            return False
1137
1138        newAttachment, distance = hit
1139
1140        self.EraseLinks(dc)
1141
1142        if to_move.GetTo() == self:
1143            oldAttachment = to_move.GetAttachmentTo()
1144        else:
1145            oldAttachment = to_move.GetAttachmentFrom()
1146
1147        # The links in a new ordering
1148        # First, add all links to the new list
1149        newOrdering = self._lines[:]
1150
1151        # Delete the line object from the list of links; we're going to move
1152        # it to another position in the list
1153        del newOrdering[newOrdering.index(to_move)]
1154
1155        old_x = -99999.9
1156        old_y = -99999.9
1157
1158        found = False
1159
1160        for line in newOrdering:
1161            if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \
1162               line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom():
1163                startX, startY, endX, endY = line.GetEnds()
1164                if line.GetTo() == self:
1165                    xp = endX
1166                    yp = endY
1167                else:
1168                    xp = startX
1169                    yp = startY
1170
1171                thisPoint = wx.RealPoint(xp, yp)
1172                lastPoint = wx.RealPoint(old_x, old_y)
1173                newPoint = wx.RealPoint(x, y)
1174
1175                if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint):
1176                    found = True
1177                    newOrdering.insert(newOrdering.index(line), to_move)
1178
1179                old_x = xp
1180                old_y = yp
1181            if found:
1182                break
1183
1184        if not found:
1185            newOrdering.append(to_move)
1186
1187        self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering)
1188        return True
1189
1190    def OnChangeAttachment(self, attachment, line, ordering):
1191        """Change attachment handler."""
1192        if line.GetTo() == self:
1193            line.SetAttachmentTo(attachment)
1194        else:
1195            line.SetAttachmentFrom(attachment)
1196
1197        self.ApplyAttachmentOrdering(ordering)
1198
1199        dc = wx.MemoryDC()
1200        dc.SelectObject(self.GetCanvas().GetBuffer())
1201        self.GetCanvas().PrepareDC(dc)
1202        self.MoveLinks(dc)
1203
1204        if not self.GetCanvas().GetQuickEditMode():
1205            self.GetCanvas().Redraw(dc)
1206
1207    # Reorders the lines according to the given list
1208    def ApplyAttachmentOrdering(self, linesToSort):
1209        """
1210        Apply the line ordering in linesToSort to the shape, to reorder
1211        the way lines are attached.
1212        """
1213        linesStore = self._lines[:]
1214
1215        self._lines = []
1216
1217        for line in linesToSort:
1218            if line in linesStore:
1219                del linesStore[linesStore.index(line)]
1220                self._lines.append(line)
1221
1222        # Now add any lines that haven't been listed in linesToSort
1223        self._lines += linesStore
1224
1225    def SortLines(self, attachment, linesToSort):
1226        """
1227        Reorder the lines coming into the node image at this attachment
1228        position, in the order in which they appear in linesToSort.
1229
1230        Any remaining lines not in the list will be added to the end.
1231        """
1232        # This is a temporary store of all the lines at this attachment
1233        # point. We'll tick them off as we've processed them.
1234        linesAtThisAttachment = []
1235
1236        for line in self._lines[:]:
1237            if line.GetTo() == self and line.GetAttachmentTo() == attachment or \
1238               line.GetFrom() == self and line.GetAttachmentFrom() == attachment:
1239                linesAtThisAttachment.append(line)
1240                del self._lines[self._lines.index(line)]
1241
1242        for line in linesToSort:
1243            if line in linesAtThisAttachment:
1244                # Done this one
1245                del linesAtThisAttachment[linesAtThisAttachment.index(line)]
1246                self._lines.append(line)
1247
1248        # Now add any lines that haven't been listed in linesToSort
1249        self._lines += linesAtThisAttachment
1250
1251    def OnHighlight(self, dc):
1252        """not implemented???"""
1253        pass
1254
1255    def OnLeftClick(self, x, y, keys = 0, attachment = 0):
1256        """The left click handler."""
1257        if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT:
1258            if self._parent:
1259                attachment, dist = self._parent.HitTest(x, y)
1260                self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment)
1261
1262    def OnRightClick(self, x, y, keys = 0, attachment = 0):
1263        """The right click handler."""
1264        if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT:
1265            attachment, dist = self._parent.HitTest(x, y)
1266            self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
1267
1268    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1269        """The drag left handler."""
1270        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1271            if self._parent:
1272                hit = self._parent.HitTest(x, y)
1273                if hit:
1274                    attachment, dist = hit
1275                self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
1276            return
1277
1278        dc = wx.ClientDC(self.GetCanvas())
1279        self.GetCanvas().PrepareDC(dc)
1280        dc.SetLogicalFunction(OGLRBLF)
1281
1282        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
1283
1284        dc.SetPen(dottedPen)
1285        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1286
1287        xx = x + DragOffsetX
1288        yy = y + DragOffsetY
1289
1290        xx, yy = self._canvas.Snap(xx, yy)
1291        w, h = self.GetBoundingBoxMax()
1292        self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1293
1294    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1295        """The begin drag left handler."""
1296        global DragOffsetX, DragOffsetY
1297
1298        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1299            if self._parent:
1300                hit = self._parent.HitTest(x, y)
1301                if hit:
1302                    attachment, dist = hit
1303                self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
1304            return
1305
1306        DragOffsetX = self._xpos - x
1307        DragOffsetY = self._ypos - y
1308
1309        dc = wx.ClientDC(self.GetCanvas())
1310        self.GetCanvas().PrepareDC(dc)
1311        dc.SetLogicalFunction(OGLRBLF)
1312
1313        # New policy: don't erase shape until end of drag.
1314        # self.Erase(dc)
1315        xx = x + DragOffsetX
1316        yy = y + DragOffsetY
1317        xx, yy = self._canvas.Snap(xx, yy)
1318
1319        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
1320        dc.SetPen(dottedPen)
1321        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1322
1323        w, h = self.GetBoundingBoxMax()
1324        self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1325        self._canvas.CaptureMouse()
1326
1327    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1328        """The end drag left handler."""
1329        if self._canvas.HasCapture():
1330            self._canvas.ReleaseMouse()
1331        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1332            if self._parent:
1333                hit = self._parent.HitTest(x, y)
1334                if hit:
1335                    attachment, dist = hit
1336                self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
1337            return
1338
1339        dc = wx.ClientDC(self.GetCanvas())
1340        self.GetCanvas().PrepareDC(dc)
1341        dc.SetLogicalFunction(wx.COPY)
1342
1343        xx = x + DragOffsetX
1344        yy = y + DragOffsetY
1345        xx, yy = self._canvas.Snap(xx, yy)
1346
1347        # New policy: erase shape at end of drag.
1348        self.Erase(dc)
1349
1350        self.Move(dc, xx, yy)
1351        if self._canvas and not self._canvas.GetQuickEditMode():
1352            self._canvas.Redraw(dc)
1353
1354    def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
1355        """The drag right handler."""
1356        if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1357            if self._parent:
1358                attachment, dist = self._parent.HitTest(x, y)
1359                self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment)
1360            return
1361
1362    def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
1363        """The begin drag right handler."""
1364        if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1365            if self._parent:
1366                attachment, dist = self._parent.HitTest(x, y)
1367                self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment)
1368            return
1369
1370    def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
1371        """The end drag right handler."""
1372        if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1373            if self._parent:
1374                attachment, dist = self._parent.HitTest(x, y)
1375                self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment)
1376            return
1377
1378    def OnDrawOutline(self, dc, x, y, w, h):
1379        """The draw outline handler."""
1380        points = [[x - w / 2.0, y - h / 2.0],
1381                [x + w / 2.0, y - h / 2.0],
1382                [x + w / 2.0, y + h / 2.0],
1383                [x - w / 2.0, y + h / 2.0],
1384                [x - w / 2.0, y - h / 2.0],
1385                ]
1386
1387        dc.DrawLines(points)
1388
1389    def Attach(self, can):
1390        """Set the shape's internal canvas pointer to point to the given canvas."""
1391        self._canvas = can
1392
1393    def Detach(self):
1394        """Disassociates the shape from its canvas."""
1395        self._canvas = None
1396
1397    def Move(self, dc, x, y, display = True):
1398        """
1399        Move the shape to the given position.
1400
1401        :param `dc`: the device context
1402        :param `x`: the x position
1403        :param `y`: the y position
1404        :param `display`: if `True` redraw
1405
1406        """
1407        old_x = self._xpos
1408        old_y = self._ypos
1409
1410        if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display):
1411            return
1412
1413        self._xpos, self._ypos = x, y
1414
1415        self.ResetControlPoints()
1416
1417        if display:
1418            self.Draw(dc)
1419
1420        self.MoveLinks(dc)
1421
1422        self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display)
1423
1424    def MoveLinks(self, dc):
1425        """Redraw all the lines attached to the shape."""
1426        self.GetEventHandler().OnMoveLinks(dc)
1427
1428    def Draw(self, dc):
1429        """
1430        Draw the whole shape and any lines attached to it.
1431
1432        Do not override this function: override OnDraw, which is called
1433        by this function.
1434        """
1435        if self._visible:
1436            self.GetEventHandler().OnDraw(dc)
1437            self.GetEventHandler().OnDrawContents(dc)
1438            self.GetEventHandler().OnDrawControlPoints(dc)
1439            self.GetEventHandler().OnDrawBranches(dc)
1440
1441    def Flash(self):
1442        """Flash the shape."""
1443        if self.GetCanvas():
1444            dc = wx.MemoryDC()
1445            dc.SelectObject(self.GetCanvas().GetBuffer())
1446            self.GetCanvas().PrepareDC(dc)
1447            dc.SetLogicalFunction(OGLRBLF)
1448            self.Draw(dc)
1449            dc.SetLogicalFunction(wx.COPY)
1450            self.Draw(dc)
1451
1452    def Show(self, show):
1453        """Set a flag indicating whether the shape should be drawn."""
1454        self._visible = show
1455        for child in self._children:
1456            child.Show(show)
1457
1458    def Erase(self, dc):
1459        """
1460        Erase the shape.
1461
1462        Does not repair damage caused to other shapes.
1463        """
1464        self.GetEventHandler().OnErase(dc)
1465        self.GetEventHandler().OnEraseControlPoints(dc)
1466        self.GetEventHandler().OnDrawBranches(dc, erase = True)
1467
1468    def EraseContents(self, dc):
1469        """
1470        Erase the shape contents, that is, the area within the shape's
1471        minimum bounding box.
1472        """
1473        self.GetEventHandler().OnEraseContents(dc)
1474
1475    def AddText(self, string):
1476        """Add a line of text to the shape's default text region."""
1477        if not self._regions:
1478            return
1479
1480        region = self._regions[0]
1481        #region.ClearText()
1482        new_line = ShapeTextLine(0, 0, string)
1483        text = region.GetFormattedText()
1484        text.append(new_line)
1485
1486        self._formatted = False
1487
1488    def SetSize(self, x, y, recursive = True):
1489        """Set the shape's size.
1490
1491        :param `x`: the x position
1492        :param `y`: the y position
1493        :param `recursive`: not used
1494
1495        """
1496        self.SetAttachmentSize(x, y)
1497        self.SetDefaultRegionSize()
1498
1499    def SetAttachmentSize(self, w, h):
1500        """
1501        Set the attachment size.
1502
1503        :param `w`: width
1504        :param `h`: height
1505
1506        """
1507        width, height = self.GetBoundingBoxMin()
1508        if width == 0:
1509            scaleX = 1.0
1510        else:
1511            scaleX = float(w) / width
1512        if height == 0:
1513            scaleY = 1.0
1514        else:
1515            scaleY = float(h) / height
1516
1517        for point in self._attachmentPoints:
1518            point._x = point._x * scaleX
1519            point._y = point._y * scaleY
1520
1521    # Add line FROM this object
1522    def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom = -1, positionTo = -1):
1523        """
1524        Add a line between this shape and the given other shape, at the
1525        specified attachment points.
1526
1527        :param `line`: the line an instance of :class:`~lib.ogl.LineShape`
1528        :param `other`: the other shape, an instance of :class:`Shape`
1529        :param `attachFrom`: the attachment from point ???
1530        :param `attachTo`: the attachment to point ???
1531        :param `positionFrom`: the from position
1532        :param `positionTo`: the to position
1533
1534        :note: The position in the list of lines at each end can also be
1535         specified, so that the line will be drawn at a particular point on
1536         its attachment point.
1537
1538        """
1539        if positionFrom == -1:
1540            if not line in self._lines:
1541                self._lines.append(line)
1542        else:
1543            # Don't preserve old ordering if we have new ordering instructions
1544            try:
1545                self._lines.remove(line)
1546            except ValueError:
1547                pass
1548            if positionFrom < len(self._lines):
1549                self._lines.insert(positionFrom, line)
1550            else:
1551                self._lines.append(line)
1552
1553        if positionTo == -1:
1554            if not other in other._lines:
1555                other._lines.append(line)
1556        else:
1557            # Don't preserve old ordering if we have new ordering instructions
1558            try:
1559                other._lines.remove(line)
1560            except ValueError:
1561                pass
1562            if positionTo < len(other._lines):
1563                other._lines.insert(positionTo, line)
1564            else:
1565                other._lines.append(line)
1566
1567        line.SetFrom(self)
1568        line.SetTo(other)
1569        line.SetAttachments(attachFrom, attachTo)
1570
1571        dc = wx.MemoryDC()
1572        dc.SelectObject(self.GetCanvas().GetBuffer())
1573        self.GetCanvas().PrepareDC(dc)
1574        self.MoveLinks(dc)
1575
1576    def RemoveLine(self, line):
1577        """
1578        Remove the given line from the shape's list of attached lines.
1579
1580        :param `line`: an instance of :class:`~lib.ogl.LineShape`
1581
1582        """
1583        if line.GetFrom() == self:
1584            line.GetTo()._lines.remove(line)
1585        else:
1586            line.GetFrom()._lines.remove(line)
1587
1588        self._lines.remove(line)
1589
1590    # Default - make 6 control points
1591    def MakeControlPoints(self):
1592        """
1593        Make a list of control points (draggable handles) appropriate to
1594        the shape.
1595        """
1596        maxX, maxY = self.GetBoundingBoxMax()
1597        minX, minY = self.GetBoundingBoxMin()
1598
1599        widthMin = minX + CONTROL_POINT_SIZE + 2
1600        heightMin = minY + CONTROL_POINT_SIZE + 2
1601
1602        # Offsets from main object
1603        top = -heightMin / 2.0
1604        bottom = heightMin / 2.0 + (maxY - minY)
1605        left = -widthMin / 2.0
1606        right = widthMin / 2.0 + (maxX - minX)
1607
1608        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL)
1609        self._canvas.AddShape(control)
1610        self._controlPoints.append(control)
1611
1612        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL)
1613        self._canvas.AddShape(control)
1614        self._controlPoints.append(control)
1615
1616        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL)
1617        self._canvas.AddShape(control)
1618        self._controlPoints.append(control)
1619
1620        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL)
1621        self._canvas.AddShape(control)
1622        self._controlPoints.append(control)
1623
1624        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL)
1625        self._canvas.AddShape(control)
1626        self._controlPoints.append(control)
1627
1628        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL)
1629        self._canvas.AddShape(control)
1630        self._controlPoints.append(control)
1631
1632        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL)
1633        self._canvas.AddShape(control)
1634        self._controlPoints.append(control)
1635
1636        control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL)
1637        self._canvas.AddShape(control)
1638        self._controlPoints.append(control)
1639
1640    def MakeMandatoryControlPoints(self):
1641        """
1642        Make the mandatory control points.
1643
1644        For example, the control point on a dividing line should appear even
1645        if the divided rectangle shape's handles should not appear (because
1646        it is the child of a composite, and children are not resizable).
1647        """
1648        for child in self._children:
1649            child.MakeMandatoryControlPoints()
1650
1651    def ResetMandatoryControlPoints(self):
1652        """Reset the mandatory control points."""
1653        for child in self._children:
1654            child.ResetMandatoryControlPoints()
1655
1656    def ResetControlPoints(self):
1657        """
1658        Reset the positions of the control points (for instance when the
1659        shape's shape has changed).
1660        """
1661        self.ResetMandatoryControlPoints()
1662
1663        if len(self._controlPoints) == 0:
1664            return
1665
1666        maxX, maxY = self.GetBoundingBoxMax()
1667        minX, minY = self.GetBoundingBoxMin()
1668
1669        widthMin = minX + CONTROL_POINT_SIZE + 2
1670        heightMin = minY + CONTROL_POINT_SIZE + 2
1671
1672        # Offsets from main object
1673        top = -heightMin / 2.0
1674        bottom = heightMin / 2.0 + (maxY - minY)
1675        left = -widthMin / 2.0
1676        right = widthMin / 2.0 + (maxX - minX)
1677
1678        self._controlPoints[0]._xoffset = left
1679        self._controlPoints[0]._yoffset = top
1680
1681        self._controlPoints[1]._xoffset = 0
1682        self._controlPoints[1]._yoffset = top
1683
1684        self._controlPoints[2]._xoffset = right
1685        self._controlPoints[2]._yoffset = top
1686
1687        self._controlPoints[3]._xoffset = right
1688        self._controlPoints[3]._yoffset = 0
1689
1690        self._controlPoints[4]._xoffset = right
1691        self._controlPoints[4]._yoffset = bottom
1692
1693        self._controlPoints[5]._xoffset = 0
1694        self._controlPoints[5]._yoffset = bottom
1695
1696        self._controlPoints[6]._xoffset = left
1697        self._controlPoints[6]._yoffset = bottom
1698
1699        self._controlPoints[7]._xoffset = left
1700        self._controlPoints[7]._yoffset = 0
1701
1702    def DeleteControlPoints(self, dc = None):
1703        """
1704        Delete the control points (or handles) for the shape.
1705
1706        Does not redraw the shape.
1707        """
1708        for control in self._controlPoints[:]:
1709            if dc:
1710                control.GetEventHandler().OnErase(dc)
1711            control.Delete()
1712            self._controlPoints.remove(control)
1713        self._controlPoints = []
1714
1715        # Children of divisions are contained objects,
1716        # so stop here
1717        if not isinstance(self, DivisionShape):
1718            for child in self._children:
1719                child.DeleteControlPoints(dc)
1720
1721    def OnDrawControlPoints(self, dc):
1722        """The draw control points handler."""
1723        if not self._drawHandles:
1724            return
1725
1726        dc.SetBrush(wx.BLACK_BRUSH)
1727        dc.SetPen(wx.BLACK_PEN)
1728
1729        for control in self._controlPoints:
1730            control.Draw(dc)
1731
1732        # Children of divisions are contained objects,
1733        # so stop here.
1734        # This test bypasses the type facility for speed
1735        # (critical when drawing)
1736
1737        if not isinstance(self, DivisionShape):
1738            for child in self._children:
1739                child.GetEventHandler().OnDrawControlPoints(dc)
1740
1741    def OnEraseControlPoints(self, dc):
1742        """The erase control points handler."""
1743        for control in self._controlPoints:
1744            control.Erase(dc)
1745
1746        if not isinstance(self, DivisionShape):
1747            for child in self._children:
1748                child.GetEventHandler().OnEraseControlPoints(dc)
1749
1750    def Select(self, select, dc = None):
1751        """
1752        Select or deselect the given shape, drawing or erasing control points
1753        (handles) as necessary.
1754
1755        :param `select`: `True` to select
1756        :param `dc`: the device context
1757
1758        """
1759        self._selected = select
1760        if select:
1761            self.MakeControlPoints()
1762            # Children of divisions are contained objects,
1763            # so stop here
1764            if not isinstance(self, DivisionShape):
1765                for child in self._children:
1766                    child.MakeMandatoryControlPoints()
1767            if dc:
1768                self.GetEventHandler().OnDrawControlPoints(dc)
1769        else:
1770            self.DeleteControlPoints(dc)
1771            if not isinstance(self, DivisionShape):
1772                for child in self._children:
1773                    child.DeleteControlPoints(dc)
1774
1775    def Selected(self):
1776        """`True` if the shape is currently selected."""
1777        return self._selected
1778
1779    def AncestorSelected(self):
1780        """`True` if the shape's ancestor is currently selected."""
1781        if self._selected:
1782            return True
1783        if not self.GetParent():
1784            return False
1785        return self.GetParent().AncestorSelected()
1786
1787    def GetNumberOfAttachments(self):
1788        """Get the number of attachment points for this shape."""
1789        # Should return the MAXIMUM attachment point id here,
1790        # so higher-level functions can iterate through all attachments,
1791        # even if they're not contiguous.
1792
1793        if len(self._attachmentPoints) == 0:
1794            return 4
1795        else:
1796            maxN = 3
1797            for point in self._attachmentPoints:
1798                if point._id > maxN:
1799                    maxN = point._id
1800            return maxN + 1
1801
1802    def AttachmentIsValid(self, attachment):
1803        """`True` if attachment is a valid attachment point."""
1804        if len(self._attachmentPoints) == 0:
1805            return attachment in range(4)
1806
1807        for point in self._attachmentPoints:
1808            if point._id == attachment:
1809                return True
1810        return False
1811
1812    def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
1813        """
1814        Get the position at which the given attachment point should be drawn.
1815
1816        :param `attachment`: the attachment ???
1817        :param `nth`: get nth attachment ???
1818        :param `no_arcs`: ???
1819        :param `line`: ???
1820
1821        If attachment isn't found among the attachment points of the shape,
1822        returns None.
1823        """
1824        if self._attachmentMode == ATTACHMENT_MODE_NONE:
1825            return self._xpos, self._ypos
1826        elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
1827            pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth)
1828            return pt[0], pt[1]
1829        elif self._attachmentMode == ATTACHMENT_MODE_EDGE:
1830            if len(self._attachmentPoints):
1831                for point in self._attachmentPoints:
1832                    if point._id == attachment:
1833                        return self._xpos + point._x, self._ypos + point._y
1834                return None
1835            else:
1836                # Assume is rectangular
1837                w, h = self.GetBoundingBoxMax()
1838                top = self._ypos + h / 2.0
1839                bottom = self._ypos - h / 2.0
1840                left = self._xpos - w / 2.0
1841                right = self._xpos + w / 2.0
1842
1843                # wtf?
1844                line and line.IsEnd(self)
1845
1846                physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1847
1848                # Simplified code
1849                if physicalAttachment == 0:
1850                    pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line)
1851                elif physicalAttachment == 1:
1852                    pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line)
1853                elif physicalAttachment == 2:
1854                    pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line)
1855                elif physicalAttachment == 3:
1856                    pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line)
1857                else:
1858                    return None
1859                return pt[0], pt[1]
1860        return None
1861
1862    def GetBoundingBoxMax(self):
1863        """
1864        Get the maximum bounding box for the shape, taking into account
1865        external features such as shadows.
1866        """
1867        ww, hh = self.GetBoundingBoxMin()
1868        if self._shadowMode != SHADOW_NONE:
1869            ww += self._shadowOffsetX
1870            hh += self._shadowOffsetY
1871        return ww, hh
1872
1873    def GetBoundingBoxMin(self):
1874        """
1875        Get the minimum bounding box for the shape, that defines the area
1876        available for drawing the contents (such as text).
1877
1878        Must be overridden.
1879        """
1880        return 0, 0
1881
1882    def HasDescendant(self, image):
1883        """
1884        Is image a descendant of this composite.
1885
1886        :param `image`: the image, is this a shape???
1887        :returns: `True` if it is a descendant
1888
1889        """
1890        if image == self:
1891            return True
1892        for child in self._children:
1893            if child.HasDescendant(image):
1894                return True
1895        return False
1896
1897    # Assuming the attachment lies along a vertical or horizontal line,
1898    # calculate the position on that point.
1899    def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line):
1900        """
1901        Assuming the attachment lies along a vertical or horizontal line,
1902        calculate the position on that point.
1903
1904        :param `pt1`: The first point of the line repesenting the edge of
1905         the shape
1906        :param `pt2`: The second point of the line representing the edge of
1907         the shape
1908        :param `nth`: The position on the edge (for example there may be 6
1909         lines at this attachment point, and this may be the 2nd line.
1910        :param `noArcs`: The number of lines at this edge.
1911        :param `line`: The line shape.
1912
1913        :note: This function expects the line to be either vertical or horizontal,
1914         and determines which.
1915
1916        """
1917        isEnd = line and line.IsEnd(self)
1918
1919        # Are we horizontal or vertical?
1920        isHorizontal = RoughlyEqual(pt1[1], pt2[1])
1921
1922        if isHorizontal:
1923            if pt1[0] > pt2[0]:
1924                firstPoint = pt2
1925                secondPoint = pt1
1926            else:
1927                firstPoint = pt1
1928                secondPoint = pt2
1929
1930            if self._spaceAttachments:
1931                if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1932                    # Align line according to the next handle along
1933                    point = line.GetNextControlPoint(self)
1934                    if point[0] < firstPoint[0]:
1935                        x = firstPoint[0]
1936                    elif point[0] > secondPoint[0]:
1937                        x = secondPoint[0]
1938                    else:
1939                        x = point[0]
1940                else:
1941                    x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1.0)
1942            else:
1943                x = (secondPoint[0] - firstPoint[0]) / 2.0 # Midpoint
1944            y = pt1[1]
1945        else:
1946            assert RoughlyEqual(pt1[0], pt2[0])
1947
1948            if pt1[1] > pt2[1]:
1949                firstPoint = pt2
1950                secondPoint = pt1
1951            else:
1952                firstPoint = pt1
1953                secondPoint = pt2
1954
1955            if self._spaceAttachments:
1956                if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1957                    # Align line according to the next handle along
1958                    point = line.GetNextControlPoint(self)
1959                    if point[1] < firstPoint[1]:
1960                        y = firstPoint[1]
1961                    elif point[1] > secondPoint[1]:
1962                        y = secondPoint[1]
1963                    else:
1964                        y = point[1]
1965                else:
1966                    y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1.0)
1967            else:
1968                y = (secondPoint[1] - firstPoint[1]) / 2.0 # Midpoint
1969            x = pt1[0]
1970
1971        return x, y
1972
1973    def GetLinePosition(self, line):
1974        """
1975        Get the zero-based position of line in the list of lines
1976        for this shape.
1977
1978        :param `line`: line to find position for
1979
1980        """
1981        try:
1982            return self._lines.index(line)
1983        except:
1984            return 0
1985
1986
1987    #            |________|
1988    #                 | <- root
1989    #                 | <- neck
1990    # shoulder1 ->---------<- shoulder2
1991    #             | | | | |
1992    #                      <- branching attachment point N-1
1993
1994    def GetBranchingAttachmentInfo(self, attachment):
1995        """
1996        Get information about where branching connections go.
1997
1998        :param `attachment`: ???
1999        :returns: `False` if there are no lines at this attachment.
2000        """
2001        physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
2002
2003        # Number of lines at this attachment
2004        lineCount = self.GetAttachmentLineCount(attachment)
2005
2006        if not lineCount:
2007            return False
2008
2009        totalBranchLength = self._branchSpacing * (lineCount - 1)
2010        root = self.GetBranchingAttachmentRoot(attachment)
2011
2012        neck = wx.RealPoint()
2013        shoulder1 = wx.RealPoint()
2014        shoulder2 = wx.RealPoint()
2015
2016        # Assume that we have attachment points 0 to 3: top, right, bottom, left
2017        if physicalAttachment == 0:
2018            neck[0] = self.GetX()
2019            neck[1] = root[1] - self._branchNeckLength
2020
2021            shoulder1[0] = root[0] - totalBranchLength / 2.0
2022            shoulder2[0] = root[0] + totalBranchLength / 2.0
2023
2024            shoulder1[1] = neck[1]
2025            shoulder2[1] = neck[1]
2026        elif physicalAttachment == 1:
2027            neck[0] = root[0] + self._branchNeckLength
2028            neck[1] = root[1]
2029
2030            shoulder1[0] = neck[0]
2031            shoulder2[0] = neck[0]
2032
2033            shoulder1[1] = neck[1] - totalBranchLength / 2.0
2034            shoulder1[1] = neck[1] + totalBranchLength / 2.0
2035        elif physicalAttachment == 2:
2036            neck[0] = self.GetX()
2037            neck[1] = root[1] + self._branchNeckLength
2038
2039            shoulder1[0] = root[0] - totalBranchLength / 2.0
2040            shoulder2[0] = root[0] + totalBranchLength / 2.0
2041
2042            shoulder1[1] = neck[1]
2043            shoulder2[1] = neck[1]
2044        elif physicalAttachment == 3:
2045            neck[0] = root[0] - self._branchNeckLength
2046            neck[1] = root[1]
2047
2048            shoulder1[0] = neck[0]
2049            shoulder2[0] = neck[0]
2050
2051            shoulder1[1] = neck[1] - totalBranchLength / 2.0
2052            shoulder2[1] = neck[1] + totalBranchLength / 2.0
2053        else:
2054            raise ValueError("Unrecognised attachment point in GetBranchingAttachmentInfo")
2055        return root, neck, shoulder1, shoulder2
2056
2057    def GetBranchingAttachmentPoint(self, attachment, n):
2058        """
2059        Get branching attachment point.
2060
2061        :param `attachment`: ???
2062        :param `n`: ???
2063
2064        """
2065        physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
2066
2067        root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
2068        pt = wx.RealPoint()
2069        stemPt = wx.RealPoint()
2070
2071        if physicalAttachment == 0:
2072            pt[1] = neck[1] - self._branchStemLength
2073            pt[0] = shoulder1[0] + n * self._branchSpacing
2074
2075            stemPt[0] = pt[0]
2076            stemPt[1] = neck[1]
2077        elif physicalAttachment == 2:
2078            pt[1] = neck[1] + self._branchStemLength
2079            pt[0] = shoulder1[0] + n * self._branchStemLength
2080
2081            stemPt[0] = pt[0]
2082            stemPt[1] = neck[1]
2083        elif physicalAttachment == 1:
2084            pt[0] = neck[0] + self._branchStemLength
2085            pt[1] = shoulder1[1] + n * self._branchSpacing
2086
2087            stemPt[0] = neck[0]
2088            stemPt[1] = pt[1]
2089        elif physicalAttachment == 3:
2090            pt[0] = neck[0] - self._branchStemLength
2091            pt[1] = shoulder1[1] + n * self._branchSpacing
2092
2093            stemPt[0] = neck[0]
2094            stemPt[1] = pt[1]
2095        else:
2096            raise ValueError("Unrecognised attachment point in GetBranchingAttachmentPoint")
2097
2098        return pt, stemPt
2099
2100    def GetAttachmentLineCount(self, attachment):
2101        """
2102        Get the number of lines at this attachment position.
2103
2104        :param `attachment`: ???
2105        :returns: the count of lines at this position
2106
2107        """
2108        count = 0
2109        for lineShape in self._lines:
2110            if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment:
2111                count += 1
2112            elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment:
2113                count += 1
2114        return count
2115
2116    def GetBranchingAttachmentRoot(self, attachment):
2117        """
2118        Get the root point at the given attachment.
2119
2120        :param `attachment`: ???
2121
2122        """
2123        physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
2124
2125        root = wx.RealPoint()
2126
2127        width, height = self.GetBoundingBoxMax()
2128
2129        # Assume that we have attachment points 0 to 3: top, right, bottom, left
2130        if physicalAttachment == 0:
2131            root[0] = self.GetX()
2132            root[1] = self.GetY() - height / 2.0
2133        elif physicalAttachment == 1:
2134            root[0] = self.GetX() + width / 2.0
2135            root[1] = self.GetY()
2136        elif physicalAttachment == 2:
2137            root[0] = self.GetX()
2138            root[1] = self.GetY() + height / 2.0
2139        elif physicalAttachment == 3:
2140            root[0] = self.GetX() - width / 2.0
2141            root[1] = self.GetY()
2142        else:
2143            raise ValueError("Unrecognised attachment point in GetBranchingAttachmentRoot")
2144
2145        return root
2146
2147    # Draw or erase the branches (not the actual arcs though)
2148    def OnDrawBranchesAttachment(self, dc, attachment, erase = False):
2149        """The draw branches attachment handler."""
2150        count = self.GetAttachmentLineCount(attachment)
2151        if count == 0:
2152            return
2153
2154        root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
2155
2156        if erase:
2157            dc.SetPen(wx.WHITE_PEN)
2158            dc.SetBrush(wx.WHITE_BRUSH)
2159        else:
2160            dc.SetPen(wx.BLACK_PEN)
2161            dc.SetBrush(wx.BLACK_BRUSH)
2162
2163        # Draw neck
2164        dc.DrawLine(root[0], root[1], neck[0], neck[1])
2165
2166        if count > 1:
2167            # Draw shoulder-to-shoulder line
2168            dc.DrawLine(shoulder1[0], shoulder1[1], shoulder2[0], shoulder2[1])
2169        # Draw all the little branches
2170        for i in range(count):
2171            pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i)
2172            dc.DrawLine(stemPt[0], stemPt[1], pt[0], pt[1])
2173
2174            if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1:
2175                blobSize = 6.0
2176                dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, blobSize, blobSize)
2177
2178    def OnDrawBranches(self, dc, erase = False):
2179        """The draw branches handler."""
2180        if self._attachmentMode != ATTACHMENT_MODE_BRANCHING:
2181            return
2182        for i in range(self.GetNumberOfAttachments()):
2183            self.OnDrawBranchesAttachment(dc, i, erase)
2184
2185    def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None):
2186        """
2187        Only get the attachment position at the _edge_ of the shape,
2188        ignoring branching mode. This is used e.g. to indicate the edge of
2189        interest, not the point on the attachment branch.
2190
2191        :param `attachment`: the attachment ???
2192        :param `nth`: get nth attachment ???
2193        :param `no_arcs`: ???
2194        :param `line`: ???
2195
2196        """
2197        oldMode = self._attachmentMode
2198
2199        # Calculate as if to edge, not branch
2200        if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
2201            self._attachmentMode = ATTACHMENT_MODE_EDGE
2202        res = self.GetAttachmentPosition(attachment, nth, no_arcs, line)
2203        self._attachmentMode = oldMode
2204
2205        return res
2206
2207    def PhysicalToLogicalAttachment(self, physicalAttachment):
2208        """
2209        Rotate the standard attachment point from physical
2210        (0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
2211
2212        :param `physicalAttachment`: ???
2213
2214        """
2215        if RoughlyEqual(self.GetRotation(), 0):
2216            i = physicalAttachment
2217        elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
2218            i = physicalAttachment - 1
2219        elif RoughlyEqual(self.GetRotation(), math.pi):
2220            i = physicalAttachment - 2
2221        elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
2222            i = physicalAttachment - 3
2223        else:
2224            # Can't handle -- assume the same
2225            return physicalAttachment
2226
2227        if i < 0:
2228            i += 4
2229
2230        return i
2231
2232    def LogicalToPhysicalAttachment(self, logicalAttachment):
2233        """
2234        Rotate the standard attachment point from logical
2235        to physical (0 is always North).
2236
2237        :param `logicalAttachment`: ???
2238
2239        """
2240        if RoughlyEqual(self.GetRotation(), 0):
2241            i = logicalAttachment
2242        elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
2243            i = logicalAttachment + 1
2244        elif RoughlyEqual(self.GetRotation(), math.pi):
2245            i = logicalAttachment + 2
2246        elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
2247            i = logicalAttachment + 3
2248        else:
2249            return logicalAttachment
2250
2251        if i > 3:
2252            i -= 4
2253
2254        return i
2255
2256    def Rotate(self, x, y, theta):
2257        """
2258        Rotate about the given axis by the given amount in radians.
2259
2260        :param `x`: the x position
2261        :param `y`: the y position
2262        :param `theta`: the theta
2263
2264        """
2265        self._rotation = theta
2266        if self._rotation < 0:
2267            self._rotation += 2 * math.pi
2268        elif self._rotation > 2 * math.pi:
2269            self._rotation -= 2 * math.pi
2270
2271    def GetBackgroundPen(self):
2272        """Return pen of the right colour for the background."""
2273        if self.GetCanvas():
2274            return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.PENSTYLE_SOLID)
2275        return WhiteBackgroundPen
2276
2277    def GetBackgroundBrush(self):
2278        """Return brush of the right colour for the background."""
2279        if self.GetCanvas():
2280            return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)
2281        return WhiteBackgroundBrush
2282
2283    def GetX(self):
2284        """Get the x position of the centre of the shape."""
2285        return self._xpos
2286
2287    def GetY(self):
2288        """Get the y position of the centre of the shape."""
2289        return self._ypos
2290
2291    def SetX(self, x):
2292        """
2293        Set the x position of the shape.
2294
2295        :param `x`: the x position
2296        """
2297        self._xpos = x
2298
2299    def SetY(self, y):
2300        """
2301        Set the y position of the shape.
2302
2303        :param `y`: the y position
2304
2305        """
2306        self._ypos = y
2307
2308    def GetParent(self):
2309        """Get the parent of this shape, if it is part of a composite."""
2310        return self._parent
2311
2312    def SetParent(self, p):
2313        """Set the parent
2314
2315        :param `p`: the parent
2316
2317        """
2318        self._parent = p
2319
2320    def GetChildren(self):
2321        """Get the list of children for this shape."""
2322        return self._children
2323
2324    def GetDrawHandles(self):
2325        """Get the list of drawhandles."""
2326        return self._drawHandles
2327
2328    def GetEventHandler(self):
2329        """Get the event handler for this shape."""
2330        return self._eventHandler
2331
2332    def SetEventHandler(self, handler):
2333        """Set the event handler for this shape.
2334
2335        :param `handler`: an instance of :class:`ShapeEvtHandler`
2336
2337        """
2338        self._eventHandler = handler
2339
2340    def Recompute(self):
2341        """
2342        Recomputes any constraints associated with the shape.
2343
2344        Normally applicable to CompositeShapes only, but harmless for
2345        other classes of Shape.
2346        """
2347        return True
2348
2349    def IsHighlighted(self):
2350        """
2351        `True` if the shape is highlighted. Shape highlighting is unimplemented.
2352        """
2353        return self._highlighted
2354
2355    def GetSensitivityFilter(self):
2356        """
2357        Get the sensitivity filter, a bitlist of values.
2358
2359        See :meth:`Shape.SetSensitivityFilter`
2360
2361        """
2362        return self._sensitivity
2363
2364    def SetFixedSize(self, x, y):
2365        """
2366        Set the shape to be fixed size.
2367
2368        :param `x`: the width
2369        :param `y`: the height
2370
2371        """
2372        self._fixedWidth = x
2373        self._fixedHeight = y
2374
2375    def GetFixedSize(self):
2376        """
2377        Return flags indicating whether the shape is of fixed size in
2378        either direction.
2379        """
2380        return self._fixedWidth, self._fixedHeight
2381
2382    def GetFixedWidth(self):
2383        """`True` if the shape cannot be resized in the horizontal plane."""
2384        return self._fixedWidth
2385
2386    def GetFixedHeight(self):
2387        """`True` if the shape cannot be resized in the vertical plane."""
2388        return self._fixedHeight
2389
2390    def SetSpaceAttachments(self, sp):
2391        """
2392        Indicate whether lines should be spaced out evenly at the point
2393        they touch the node.
2394
2395        :param `sp`: if `True` space out evently, else they should join at a
2396         single point.
2397
2398        """
2399        self._spaceAttachments = sp
2400
2401    def GetSpaceAttachments(self):
2402        """
2403        Get whether lines should be spaced out evenly at the point they
2404        touch the node (True), or whether they should join at a single point
2405        (False).
2406        """
2407        return self._spaceAttachments
2408
2409    def SetCentreResize(self, cr):
2410        """
2411        Specify whether the shape is to be resized from the centre (the
2412        centre stands still) or from the corner or side being dragged (the
2413        other corner or side stands still).
2414        """
2415        self._centreResize = cr
2416
2417    def GetCentreResize(self):
2418        """
2419        `True` if the shape is to be resized from the centre (the centre stands
2420        still), or `False` if from the corner or side being dragged (the other
2421        corner or side stands still)
2422
2423        """
2424        return self._centreResize
2425
2426    def SetMaintainAspectRatio(self, ar):
2427        """
2428        Set whether a shape that resizes should not change the aspect ratio
2429        (width and height should be in the original proportion).
2430
2431        """
2432        self._maintainAspectRatio = ar
2433
2434    def GetMaintainAspectRatio(self):
2435        """`True` if shape keeps aspect ratio during resize."""
2436        return self._maintainAspectRatio
2437
2438    def GetLines(self):
2439        """Return the list of lines connected to this shape."""
2440        return self._lines
2441
2442    def SetDisableLabel(self, flag):
2443        """Set flag to `True` to stop the default region being shown."""
2444        self._disableLabel = flag
2445
2446    def GetDisableLabel(self):
2447        """`True` if the default region will not be shown, `False` otherwise."""
2448        return self._disableLabel
2449
2450    def SetAttachmentMode(self, mode):
2451        """
2452        Set the attachment mode.
2453
2454        :param `mode`: if `True` attachment points will be significant when
2455         drawing lines to and from this shape. If `False` lines will be drawn
2456         as if to the centre of the shape.
2457
2458        """
2459        self._attachmentMode = mode
2460
2461    def GetAttachmentMode(self):
2462        """
2463        Get the attachment mode.
2464
2465        See :meth:`Shape.SetAttachmentMode`
2466        """
2467        return self._attachmentMode
2468
2469    def SetId(self, i):
2470        """Set the integer identifier for this shape."""
2471        self._id = i
2472
2473    def GetId(self):
2474        """Get the integer identifier for this shape."""
2475        return self._id
2476
2477    def IsShown(self):
2478        """
2479        `True` if the shape is in a visible state, `False` otherwise.
2480
2481        :note: That this has nothing to do with whether the window is hidden
2482         or the shape has scrolled off the canvas; it refers to the internal
2483         visibility flag.
2484
2485        """
2486        return self._visible
2487
2488    def GetPen(self):
2489        """Get the pen used for drawing the shape's outline."""
2490        return self._pen
2491
2492    def GetBrush(self):
2493        """Get the brush used for filling the shape."""
2494        return self._brush
2495
2496    def GetNumberOfTextRegions(self):
2497        """Get the number of text regions for this shape."""
2498        return len(self._regions)
2499
2500    def GetRegions(self):
2501        """Get the list of ShapeRegions."""
2502        return self._regions
2503
2504    # Control points ('handles') redirect control to the actual shape, to
2505    # make it easier to override sizing behaviour.
2506    def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
2507        """The sizing drag left handler."""
2508        bound_x, bound_y = self.GetBoundingBoxMin()
2509
2510        dc = wx.MemoryDC()
2511        dc.SelectObject(self.GetCanvas().GetBuffer())
2512        self.GetCanvas().PrepareDC(dc)
2513        dc.SetLogicalFunction(OGLRBLF)
2514
2515        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
2516        dc.SetPen(dottedPen)
2517        dc.SetBrush(wx.TRANSPARENT_BRUSH)
2518
2519        if self.GetCentreResize():
2520            # Maintain the same centre point
2521            new_width = 2.0 * abs(x - self.GetX())
2522            new_height = 2.0 * abs(y - self.GetY())
2523
2524            # Constrain sizing according to what control point you're dragging
2525            if pt._type == CONTROL_POINT_HORIZONTAL:
2526                if self.GetMaintainAspectRatio():
2527                    new_height = bound_y * (new_width / bound_x)
2528                else:
2529                    new_height = bound_y
2530            elif pt._type == CONTROL_POINT_VERTICAL:
2531                if self.GetMaintainAspectRatio():
2532                    new_width = bound_x * (new_height / bound_y)
2533                else:
2534                    new_width = bound_x
2535            elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2536                new_height = bound_y * (new_width / bound_x)
2537
2538            if self.GetFixedWidth():
2539                new_width = bound_x
2540
2541            if self.GetFixedHeight():
2542                new_height = bound_y
2543
2544            pt._controlPointDragEndWidth = new_width
2545            pt._controlPointDragEndHeight = new_height
2546
2547            self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
2548        else:
2549            # Don't maintain the same centre point
2550            newX1 = min(pt._controlPointDragStartX, x)
2551            newY1 = min(pt._controlPointDragStartY, y)
2552            newX2 = max(pt._controlPointDragStartX, x)
2553            newY2 = max(pt._controlPointDragStartY, y)
2554            if pt._type == CONTROL_POINT_HORIZONTAL:
2555                newY1 = pt._controlPointDragStartY
2556                newY2 = newY1 + pt._controlPointDragStartHeight
2557            elif pt._type == CONTROL_POINT_VERTICAL:
2558                newX1 = pt._controlPointDragStartX
2559                newX2 = newX1 + pt._controlPointDragStartWidth
2560            elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
2561                newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
2562                if self.GetY() > pt._controlPointDragStartY:
2563                    newY2 = newY1 + newH
2564                else:
2565                    newY1 = newY2 - newH
2566
2567            newWidth = float(newX2 - newX1)
2568            newHeight = float(newY2 - newY1)
2569
2570            if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2571                newWidth = bound_x * (newHeight / bound_y)
2572
2573            if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2574                newHeight = bound_y * (newWidth / bound_x)
2575
2576            pt._controlPointDragPosX = newX1 + newWidth / 2.0
2577            pt._controlPointDragPosY = newY1 + newHeight / 2.0
2578            if self.GetFixedWidth():
2579                newWidth = bound_x
2580
2581            if self.GetFixedHeight():
2582                newHeight = bound_y
2583
2584            pt._controlPointDragEndWidth = newWidth
2585            pt._controlPointDragEndHeight = newHeight
2586            self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2587
2588    def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2589        """The sizing begin drag left handler."""
2590        self._canvas.CaptureMouse()
2591
2592        dc = wx.MemoryDC()
2593        dc.SelectObject(self.GetCanvas().GetBuffer())
2594        self.GetCanvas().PrepareDC(dc)
2595        dc.SetLogicalFunction(OGLRBLF)
2596
2597        bound_x, bound_y = self.GetBoundingBoxMin()
2598        self.GetEventHandler().OnBeginSize(bound_x, bound_y)
2599
2600        # Choose the 'opposite corner' of the object as the stationary
2601        # point in case this is non-centring resizing.
2602        if pt.GetX() < self.GetX():
2603            pt._controlPointDragStartX = self.GetX() + bound_x / 2.0
2604        else:
2605            pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
2606
2607        if pt.GetY() < self.GetY():
2608            pt._controlPointDragStartY = self.GetY() + bound_y / 2.0
2609        else:
2610            pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
2611
2612        if pt._type == CONTROL_POINT_HORIZONTAL:
2613            pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
2614        elif pt._type == CONTROL_POINT_VERTICAL:
2615            pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
2616
2617        # We may require the old width and height
2618        pt._controlPointDragStartWidth = bound_x
2619        pt._controlPointDragStartHeight = bound_y
2620
2621        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
2622        dc.SetPen(dottedPen)
2623        dc.SetBrush(wx.TRANSPARENT_BRUSH)
2624
2625        if self.GetCentreResize():
2626            new_width = 2.0 * abs(x - self.GetX())
2627            new_height = 2.0 * abs(y - self.GetY())
2628
2629            # Constrain sizing according to what control point you're dragging
2630            if pt._type == CONTROL_POINT_HORIZONTAL:
2631                if self.GetMaintainAspectRatio():
2632                    new_height = bound_y * (new_width / bound_x)
2633                else:
2634                    new_height = bound_y
2635            elif pt._type == CONTROL_POINT_VERTICAL:
2636                if self.GetMaintainAspectRatio():
2637                    new_width = bound_x * (new_height / bound_y)
2638                else:
2639                    new_width = bound_x
2640            elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2641                new_height = bound_y * (new_width / bound_x)
2642
2643            if self.GetFixedWidth():
2644                new_width = bound_x
2645
2646            if self.GetFixedHeight():
2647                new_height = bound_y
2648
2649            pt._controlPointDragEndWidth = new_width
2650            pt._controlPointDragEndHeight = new_height
2651            self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
2652        else:
2653            # Don't maintain the same centre point
2654            newX1 = min(pt._controlPointDragStartX, x)
2655            newY1 = min(pt._controlPointDragStartY, y)
2656            newX2 = max(pt._controlPointDragStartX, x)
2657            newY2 = max(pt._controlPointDragStartY, y)
2658            if pt._type == CONTROL_POINT_HORIZONTAL:
2659                newY1 = pt._controlPointDragStartY
2660                newY2 = newY1 + pt._controlPointDragStartHeight
2661            elif pt._type == CONTROL_POINT_VERTICAL:
2662                newX1 = pt._controlPointDragStartX
2663                newX2 = newX1 + pt._controlPointDragStartWidth
2664            elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
2665                newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
2666                if pt.GetY() > pt._controlPointDragStartY:
2667                    newY2 = newY1 + newH
2668                else:
2669                    newY1 = newY2 - newH
2670
2671            newWidth = float(newX2 - newX1)
2672            newHeight = float(newY2 - newY1)
2673
2674            if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2675                newWidth = bound_x * (newHeight / bound_y)
2676
2677            if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2678                newHeight = bound_y * (newWidth / bound_x)
2679
2680            pt._controlPointDragPosX = newX1 + newWidth / 2.0
2681            pt._controlPointDragPosY = newY1 + newHeight / 2.0
2682            if self.GetFixedWidth():
2683                newWidth = bound_x
2684
2685            if self.GetFixedHeight():
2686                newHeight = bound_y
2687
2688            pt._controlPointDragEndWidth = newWidth
2689            pt._controlPointDragEndHeight = newHeight
2690            self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2691
2692    def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2693        """The sizing end drag left handler."""
2694        dc = wx.MemoryDC()
2695        dc.SelectObject(self.GetCanvas().GetBuffer())
2696        self.GetCanvas().PrepareDC(dc)
2697
2698        if self._canvas.HasCapture():
2699            self._canvas.ReleaseMouse()
2700        dc.SetLogicalFunction(wx.COPY)
2701        self.Recompute()
2702        self.ResetControlPoints()
2703
2704        self.Erase(dc)
2705
2706        self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight)
2707
2708        # The next operation could destroy this control point (it does for
2709        # label objects, via formatting the text), so save all values we're
2710        # going to use, or we'll be accessing garbage.
2711
2712        #return
2713
2714        if self.GetCentreResize():
2715            self.Move(dc, self.GetX(), self.GetY())
2716        else:
2717            self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY)
2718
2719        # Recursively redraw links if we have a composite
2720        if len(self.GetChildren()):
2721            self.DrawLinks(dc, -1, True)
2722
2723        width, height = self.GetBoundingBoxMax()
2724        self.GetEventHandler().OnEndSize(width, height)
2725
2726        if not self._canvas.GetQuickEditMode() and pt._eraseObject:
2727            self._canvas.Redraw(dc)
2728
2729
2730class RectangleShape(Shape):
2731    """
2732    The :class:`wx.RectangleShape` class has rounded or square corners.
2733    """
2734    def __init__(self, w = 0.0, h = 0.0):
2735        """
2736        Default class constructor
2737
2738        :param float `w`: the width
2739        :param float `h`: the height
2740
2741        """
2742        Shape.__init__(self)
2743        self._width = w
2744        self._height = h
2745        self._cornerRadius = 0.0
2746        self.SetDefaultRegionSize()
2747
2748    def OnDraw(self, dc):
2749        """The draw handler."""
2750        x1 = self._xpos - self._width / 2.0
2751        y1 = self._ypos - self._height / 2.0
2752
2753        if self._shadowMode != SHADOW_NONE:
2754            if self._shadowBrush:
2755                dc.SetBrush(self._shadowBrush)
2756            dc.SetPen(TransparentPen)
2757
2758            if self._cornerRadius:
2759                dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
2760            else:
2761                dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
2762
2763        if self._pen:
2764            if self._pen.GetWidth() == 0:
2765                dc.SetPen(TransparentPen)
2766            else:
2767                dc.SetPen(self._pen)
2768        if self._brush:
2769            dc.SetBrush(self._brush)
2770
2771        if self._cornerRadius:
2772            dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
2773        else:
2774            dc.DrawRectangle(x1, y1, self._width, self._height)
2775
2776    def GetBoundingBoxMin(self):
2777        """Get the bounding box minimum."""
2778        return self._width, self._height
2779
2780    def SetSize(self, x, y, recursive = False):
2781        """
2782        Set the size.
2783
2784        :param `x`: the width
2785        :param `y`: the height
2786        :param `recursive`: not used
2787
2788        """
2789        self.SetAttachmentSize(x, y)
2790        self._width = max(x, 1)
2791        self._height = max(y, 1)
2792        self.SetDefaultRegionSize()
2793
2794    def GetCornerRadius(self):
2795        """Get the radius of the rectangle's rounded corners."""
2796        return self._cornerRadius
2797
2798    def SetCornerRadius(self, rad):
2799        """
2800        Set the radius of the rectangle's rounded corners.
2801
2802        :param `rad`: If the radius is zero, a non-rounded rectangle will be
2803         drawn. If the radius is negative, the value is the proportion of the
2804         smaller dimension of the rectangle.
2805
2806        """
2807        self._cornerRadius = rad
2808
2809    # Assume (x1, y1) is centre of box (most generally, line end at box)
2810    def GetPerimeterPoint(self, x1, y1, x2, y2):
2811        """
2812        Get the perimeter point.
2813
2814        :param `x1`: ???
2815        :param `y1`: ???
2816        :param `x2`: ???
2817        :param `y2`: ???
2818
2819        """
2820        bound_x, bound_y = self.GetBoundingBoxMax()
2821        return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2)
2822
2823    def GetWidth(self):
2824        """Get the width."""
2825        return self._width
2826
2827    def GetHeight(self):
2828        """Get the height."""
2829        return self._height
2830
2831    def SetWidth(self, w):
2832        """
2833        Set the width.
2834
2835        :param `w`: width to be set
2836
2837        """
2838        self._width = w
2839
2840    def SetHeight(self, h):
2841        """
2842        Set the heigth.
2843
2844        :param `h`: heigth to be set
2845
2846        """
2847        self._height = h
2848
2849
2850class PolygonShape(Shape):
2851    """
2852    The :class:`PolygonShape` class shape is defined by a number of points
2853    passed to the object's constructor. It can be used to create new shapes
2854    such as diamonds and triangles.
2855    """
2856    def __init__(self):
2857        """
2858        Default class constructor
2859
2860        Does not follow above statement, should it? or is Create called
2861        automagically?
2862
2863        """
2864        Shape.__init__(self)
2865
2866        self._points = None
2867        self._originalPoints = None
2868
2869    def Create(self, the_points = None):
2870        """
2871        Takes a list of :class:`wx.Points` or tuples; each point is an offset
2872        from the centre.
2873        """
2874        self.ClearPoints()
2875
2876        if not the_points:
2877            self._originalPoints = []
2878            self._points = []
2879        else:
2880            self._originalPoints = the_points
2881
2882            # Duplicate the list of points
2883            self._points = []
2884            for point in the_points:
2885                new_point = wx.Point(point[0], point[1])
2886                self._points.append(new_point)
2887            self.CalculateBoundingBox()
2888            self._originalWidth = self._boundWidth
2889            self._originalHeight = self._boundHeight
2890            self.SetDefaultRegionSize()
2891
2892    def ClearPoints(self):
2893        """Clear the points."""
2894        self._points = []
2895        self._originalPoints = []
2896
2897    # Width and height. Centre of object is centre of box
2898    def GetBoundingBoxMin(self):
2899        """Get minimum bounding box."""
2900        return self._boundWidth, self._boundHeight
2901
2902    def GetPoints(self):
2903        """Return the internal list of polygon vertices."""
2904        return self._points
2905
2906    def GetOriginalPoints(self):
2907        """Get the original points."""
2908        return self._originalPoints
2909
2910    def GetOriginalWidth(self):
2911        """Get the original width."""
2912        return self._originalWidth
2913
2914    def GetOriginalHeight(self):
2915        """Get the original height."""
2916        return self._originalHeight
2917
2918    def SetOriginalWidth(self, w):
2919        """
2920        Set the original width.
2921
2922        :param `w`: the width
2923
2924        """
2925        self._originalWidth = w
2926
2927    def SetOriginalHeight(self, h):
2928        """
2929        Set the original height.
2930
2931        :param `w`: the height
2932
2933        """
2934        self._originalHeight = h
2935
2936    def CalculateBoundingBox(self):
2937        """Calculate the bounding box."""
2938        left = 10000
2939        right = -10000
2940        top = 10000
2941        bottom = -10000
2942
2943        for point in self._points:
2944            if point[0] < left:
2945                left = point[0]
2946            if point[0] > right:
2947                right = point[0]
2948
2949            if point[1] < top:
2950                top = point[1]
2951            if point[1] > bottom:
2952                bottom = point[1]
2953
2954        self._boundWidth = right - left
2955        self._boundHeight = bottom - top
2956
2957    def CalculatePolygonCentre(self):
2958        """
2959        Recalculates the centre of the polygon, and
2960        readjusts the point offsets accordingly.
2961        Necessary since the centre of the polygon
2962        is expected to be the real centre of the bounding
2963        box.
2964        """
2965        left = 10000
2966        right = -10000
2967        top = 10000
2968        bottom = -10000
2969
2970        for point in self._points:
2971            if point[0] < left:
2972                left = point[0]
2973            if point[0] > right:
2974                right = point[0]
2975
2976            if point[1] < top:
2977                top = point[1]
2978            if point[1] > bottom:
2979                bottom = point[1]
2980
2981        bwidth = right - left
2982        bheight = bottom - top
2983
2984        newCentreX = left + bwidth / 2.0
2985        newCentreY = top + bheight / 2.0
2986
2987        for i in range(len(self._points)):
2988            self._points[i] = self._points[i][0] - newCentreX, self._points[i][1] - newCentreY
2989        self._xpos += newCentreX
2990        self._ypos += newCentreY
2991
2992    def HitTest(self, x, y):
2993        """Hit text
2994
2995        :param `x`: the x position
2996        :param `y`: the y position
2997
2998        """
2999        # Imagine four lines radiating from this point. If all of these lines
3000        # hit the polygon, we're inside it, otherwise we're not. Obviously
3001        # we'd need more radiating lines to be sure of correct results for
3002        # very strange (concave) shapes.
3003        endPointsX = [x, x + 1000, x, x - 1000]
3004        endPointsY = [y - 1000, y, y + 1000, y]
3005
3006        xpoints = []
3007        ypoints = []
3008
3009        for point in self._points:
3010            xpoints.append(point[0] + self._xpos)
3011            ypoints.append(point[1] + self._ypos)
3012
3013        # We assume it's inside the polygon UNLESS one or more
3014        # lines don't hit the outline.
3015        isContained = True
3016
3017        for i in range(4):
3018            if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]):
3019                isContained = False
3020
3021        if not isContained:
3022            return False
3023
3024        nearest_attachment = 0
3025
3026        # If a hit, check the attachment points within the object
3027        nearest = 999999
3028
3029        for i in range(self.GetNumberOfAttachments()):
3030            e = self.GetAttachmentPositionEdge(i)
3031            if e:
3032                xp, yp = e
3033                l = math.sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y))
3034                if l < nearest:
3035                    nearest = l
3036                    nearest_attachment = i
3037
3038        return nearest_attachment, nearest
3039
3040    # Really need to be able to reset the shape! Otherwise, if the
3041    # points ever go to zero, we've lost it, and can't resize.
3042    def SetSize(self, new_width, new_height, recursive = True):
3043        """
3044        Set the size
3045
3046        :param `new_width`: the width
3047        :param `new_height`: the height
3048        :param `recursive`: not used
3049
3050        """
3051        self.SetAttachmentSize(new_width, new_height)
3052
3053        # Multiply all points by proportion of new size to old size
3054        x_proportion = abs(float(new_width) / self._originalWidth)
3055        y_proportion = abs(float(new_height) / self._originalHeight)
3056
3057        for i in range(max(len(self._points), len(self._originalPoints))):
3058            self._points[i] = wx.Point(self._originalPoints[i][0] * x_proportion, self._originalPoints[i][1] * y_proportion)
3059
3060        self._boundWidth = abs(new_width)
3061        self._boundHeight = abs(new_height)
3062        self.SetDefaultRegionSize()
3063
3064    # Make the original points the same as the working points
3065    def UpdateOriginalPoints(self):
3066        """
3067        If we've changed the shape, must make the original points match the
3068        working points with this function.
3069
3070        """
3071        self._originalPoints = []
3072
3073        for point in self._points:
3074            original_point = wx.RealPoint(point[0], point[1])
3075            self._originalPoints.append(original_point)
3076
3077        self.CalculateBoundingBox()
3078        self._originalWidth = self._boundWidth
3079        self._originalHeight = self._boundHeight
3080
3081    def AddPolygonPoint(self, pos):
3082        """
3083        Add a control point after the given point.
3084
3085        :param `pos`: position of point
3086
3087        """
3088        try:
3089            firstPoint = self._points[pos]
3090        except ValueError:
3091            firstPoint = self._points[0]
3092
3093        try:
3094            secondPoint = self._points[pos + 1]
3095        except ValueError:
3096            secondPoint = self._points[0]
3097
3098        x = (secondPoint[0] - firstPoint[0]) / 2.0 + firstPoint[0]
3099        y = (secondPoint[1] - firstPoint[1]) / 2.0 + firstPoint[1]
3100        point = wx.RealPoint(x, y)
3101
3102        if pos >= len(self._points) - 1:
3103            self._points.append(point)
3104        else:
3105            self._points.insert(pos + 1, point)
3106
3107        self.UpdateOriginalPoints()
3108
3109        if self._selected:
3110            self.DeleteControlPoints()
3111            self.MakeControlPoints()
3112
3113    def DeletePolygonPoint(self, pos):
3114        """
3115
3116        Delete the given control point.
3117
3118        :param `pos`: position of point
3119
3120        """
3121        if pos < len(self._points):
3122            del self._points[pos]
3123            self.UpdateOriginalPoints()
3124            if self._selected:
3125                self.DeleteControlPoints()
3126                self.MakeControlPoints()
3127
3128    # Assume (x1, y1) is centre of box (most generally, line end at box)
3129    def GetPerimeterPoint(self, x1, y1, x2, y2):
3130        """
3131        Get the perimeter point.
3132
3133        :param `x1`: the x1 position
3134        :param `y1`: the y1 position
3135        :param `x2`: the x2 position
3136        :param `y2`: the y2 position
3137
3138        """
3139        # First check for situation where the line is vertical,
3140        # and we would want to connect to a point on that vertical --
3141        # oglFindEndForPolyline can't cope with this (the arrow
3142        # gets drawn to the wrong place).
3143        if self._attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
3144            # Look for the point we'd be connecting to. This is
3145            # a heuristic...
3146            for point in self._points:
3147                if point[0] == 0:
3148                    if y2 > y1 and point[1] > 0:
3149                        return point[0] + self._xpos, point[1] + self._ypos
3150                    elif y2 < y1 and point[1] < 0:
3151                        return point[0] + self._xpos, point[1] + self._ypos
3152
3153        xpoints = []
3154        ypoints = []
3155        for point in self._points:
3156            xpoints.append(point[0] + self._xpos)
3157            ypoints.append(point[1] + self._ypos)
3158
3159        return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2)
3160
3161    def OnDraw(self, dc):
3162        """The draw handler."""
3163        if self._shadowMode != SHADOW_NONE:
3164            if self._shadowBrush:
3165                dc.SetBrush(self._shadowBrush)
3166            dc.SetPen(TransparentPen)
3167
3168            dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY)
3169
3170        if self._pen:
3171            if self._pen.GetWidth() == 0:
3172                dc.SetPen(TransparentPen)
3173            else:
3174                dc.SetPen(self._pen)
3175        if self._brush:
3176            dc.SetBrush(self._brush)
3177        dc.DrawPolygon(self._points, self._xpos, self._ypos)
3178
3179    def OnDrawOutline(self, dc, x, y, w, h):
3180        """The draw outline handler."""
3181        dc.SetBrush(wx.TRANSPARENT_BRUSH)
3182        # Multiply all points by proportion of new size to old size
3183        x_proportion = abs(float(w) / self._originalWidth)
3184        y_proportion = abs(float(h) / self._originalHeight)
3185
3186        intPoints = []
3187        for point in self._originalPoints:
3188            intPoints.append(wx.Point(x_proportion * point[0], y_proportion * point[1]))
3189        dc.DrawPolygon(intPoints, x, y)
3190
3191    # Make as many control points as there are vertices
3192    def MakeControlPoints(self):
3193        """Make control points."""
3194        for point in self._points:
3195            control = PolygonControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point, point[0], point[1])
3196            self._canvas.AddShape(control)
3197            self._controlPoints.append(control)
3198
3199    def ResetControlPoints(self):
3200        """Reset control points."""
3201        for i in range(min(len(self._points), len(self._controlPoints))):
3202            point = self._points[i]
3203            self._controlPoints[i]._xoffset = point[0]
3204            self._controlPoints[i]._yoffset = point[1]
3205            self._controlPoints[i].polygonVertex = point
3206
3207    def GetNumberOfAttachments(self):
3208        """Get number of attachments."""
3209        maxN = max(len(self._points) - 1, 0)
3210        for point in self._attachmentPoints:
3211            if point._id > maxN:
3212                maxN = point._id
3213        return maxN + 1
3214
3215    def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
3216        """
3217        Get attachment position.
3218
3219        :param `attachment`: the attachment ???
3220        :param `nth`: get nth attachment ???
3221        :param `no_arcs`: ???
3222        :param `line`: ???
3223
3224        """
3225        if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment < len(self._points):
3226            point = self._points[0]
3227            return point[0] + self._xpos, point[1] + self._ypos
3228        return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
3229
3230    def AttachmentIsValid(self, attachment):
3231        """
3232        Is attachment valid?
3233
3234        :param `attachment`: ???
3235
3236        """
3237        if not self._points:
3238            return False
3239
3240        if attachment >= 0 and attachment < len(self._points):
3241            return True
3242
3243        for point in self._attachmentPoints:
3244            if point._id == attachment:
3245                return True
3246
3247        return False
3248
3249    def Rotate(self, x, y, theta):
3250        """
3251        Rotate about the given axis by the given amount in radians.
3252
3253        :param `x`: the x position
3254        :param `y`: the y position
3255        :param `theta`: the theta
3256
3257        """
3258
3259        actualTheta = theta - self._rotation
3260
3261        # Rotate attachment points
3262        sinTheta = math.sin(actualTheta)
3263        cosTheta = math.cos(actualTheta)
3264
3265        for point in self._attachmentPoints:
3266            x1 = point._x
3267            y1 = point._y
3268
3269            point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
3270            point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
3271
3272        for i in range(len(self._points)):
3273            x1, y1 = self._points[i]
3274
3275            self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
3276
3277        for i in range(len(self._originalPoints)):
3278            x1, y1 = self._originalPoints[i]
3279
3280            self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
3281
3282        # Added by Pierre Hjälm. If we don't do this the outline will be
3283        # the wrong size. Hopefully it won't have any ill effects.
3284        self.UpdateOriginalPoints()
3285
3286        self._rotation = theta
3287
3288        self.CalculatePolygonCentre()
3289        self.CalculateBoundingBox()
3290        self.ResetControlPoints()
3291
3292    # Control points ('handles') redirect control to the actual shape, to
3293    # make it easier to override sizing behaviour.
3294    def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
3295        """The sizing drag left handler."""
3296        dc = wx.MemoryDC()
3297        dc.SelectObject(self.GetCanvas().GetBuffer())
3298        self.GetCanvas().PrepareDC(dc)
3299        dc.SetLogicalFunction(OGLRBLF)
3300
3301        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
3302        dc.SetPen(dottedPen)
3303        dc.SetBrush(wx.TRANSPARENT_BRUSH)
3304
3305        # Code for CTRL-drag in C++ version commented out
3306
3307        pt.CalculateNewSize(x, y)
3308
3309        self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
3310
3311    def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
3312        """The sizing begin drag left handler."""
3313        dc = wx.MemoryDC()
3314        dc.SelectObject(self.GetCanvas().GetBuffer())
3315        self.GetCanvas().PrepareDC(dc)
3316        dc.SetLogicalFunction(OGLRBLF)
3317
3318        self.Erase(dc)
3319
3320        bound_x, bound_y = self.GetBoundingBoxMin()
3321
3322        dist = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
3323
3324        pt._originalDistance = dist
3325        pt._originalSize[0] = bound_x
3326        pt._originalSize[1] = bound_y
3327
3328        if pt._originalDistance == 0:
3329            pt._originalDistance = 0.0001
3330
3331        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
3332        dc.SetPen(dottedPen)
3333        dc.SetBrush(wx.TRANSPARENT_BRUSH)
3334
3335        # Code for CTRL-drag in C++ version commented out
3336
3337        pt.CalculateNewSize(x, y)
3338
3339        self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
3340
3341        self._canvas.CaptureMouse()
3342
3343    def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
3344        """The sizing end drag left handler."""
3345        dc = wx.MemoryDC()
3346        dc.SelectObject(self.GetCanvas().GetBuffer())
3347        self.GetCanvas().PrepareDC(dc)
3348
3349        if self._canvas.HasCapture():
3350            self._canvas.ReleaseMouse()
3351        dc.SetLogicalFunction(wx.COPY)
3352
3353        # If we're changing shape, must reset the original points
3354        if keys & KEY_CTRL:
3355            self.CalculateBoundingBox()
3356            self.CalculatePolygonCentre()
3357        else:
3358            self.SetSize(pt.GetNewSize()[0], pt.GetNewSize()[1])
3359
3360        self.Recompute()
3361        self.ResetControlPoints()
3362        self.Move(dc, self.GetX(), self.GetY())
3363        if not self._canvas.GetQuickEditMode():
3364            self._canvas.Redraw(dc)
3365
3366
3367class EllipseShape(Shape):
3368    """
3369    The :class:`EllipseShape` class behaves similarly to the
3370    :class`RectangleShape` but is elliptical.
3371
3372    """
3373    def __init__(self, w, h):
3374        """
3375        Default class constructor
3376
3377        :param `w`: the width
3378        :param `h`: the height
3379
3380        """
3381        Shape.__init__(self)
3382        self._width = w
3383        self._height = h
3384        self.SetDefaultRegionSize()
3385
3386    def GetBoundingBoxMin(self):
3387        """Get the minimum bounding box."""
3388        return self._width, self._height
3389
3390    def GetPerimeterPoint(self, x1, y1, x2, y2):
3391        """
3392        Get the perimeter point.
3393
3394        :param `x1`: the x1 position
3395        :param `y1`: the y1 position
3396        :param `x2`: the x2 position
3397        :param `y2`: the y2 position
3398
3399        """
3400        bound_x, bound_y = self.GetBoundingBoxMax()
3401
3402        return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1)
3403
3404    def GetWidth(self):
3405        """Get the width."""
3406        return self._width
3407
3408    def GetHeight(self):
3409        """Get the height."""
3410        return self._height
3411
3412    def SetWidth(self, w):
3413        """
3414        Set the width.
3415
3416        :param `w`: the width
3417
3418        """
3419        self._width = w
3420
3421    def SetHeight(self, h):
3422        """
3423        Set the height.
3424
3425        :param `h`: the height
3426
3427        """
3428        self._height = h
3429
3430    def OnDraw(self, dc):
3431        """The draw handler."""
3432        if self._shadowMode != SHADOW_NONE:
3433            if self._shadowBrush:
3434                dc.SetBrush(self._shadowBrush)
3435            dc.SetPen(TransparentPen)
3436            dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0 + self._shadowOffsetX,
3437                           self._ypos - self.GetHeight() / 2.0 + self._shadowOffsetY,
3438                           self.GetWidth(), self.GetHeight())
3439
3440        if self._pen:
3441            if self._pen.GetWidth() == 0:
3442                dc.SetPen(TransparentPen)
3443            else:
3444                dc.SetPen(self._pen)
3445        if self._brush:
3446            dc.SetBrush(self._brush)
3447        dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0, self._ypos - self.GetHeight() / 2.0, self.GetWidth(), self.GetHeight())
3448
3449    def SetSize(self, x, y, recursive = True):
3450        """
3451        Set the size.
3452
3453        :param `x`: the width
3454        :param `y`: the height
3455        :recursive: not used
3456
3457        """
3458        self.SetAttachmentSize(x, y)
3459        self._width = x
3460        self._height = y
3461        self.SetDefaultRegionSize()
3462
3463    def GetNumberOfAttachments(self):
3464        """Get number of attachments."""
3465        return Shape.GetNumberOfAttachments(self)
3466
3467    # There are 4 attachment points on an ellipse - 0 = top, 1 = right,
3468    # 2 = bottom, 3 = left.
3469    def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
3470        """
3471        Get attachment position.
3472
3473        :param `attachment`: the attachment ???
3474        :param `nth`: get nth attachment ???
3475        :param `no_arcs`: ???
3476        :param `line`: ???
3477
3478        """
3479        if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
3480            return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
3481
3482        if self._attachmentMode != ATTACHMENT_MODE_NONE:
3483            top = self._ypos + self._height / 2.0
3484            bottom = self._ypos - self._height / 2.0
3485            left = self._xpos - self._width / 2.0
3486            right = self._xpos + self._width / 2.0
3487
3488            physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
3489
3490            if physicalAttachment == 0:
3491                if self._spaceAttachments:
3492                    x = left + (nth + 1) * self._width / (no_arcs + 1.0)
3493                else:
3494                    x = self._xpos
3495                y = top
3496                # We now have the point on the bounding box: but get the point
3497                # on the ellipse by imagining a vertical line from
3498                # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting
3499                # the ellipse.
3500
3501                return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
3502            elif physicalAttachment == 1:
3503                x = right
3504                if self._spaceAttachments:
3505                    y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
3506                else:
3507                    y = self._ypos
3508                return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos + self._width + 500, y, self._xpos, y)
3509            elif physicalAttachment == 2:
3510                if self._spaceAttachments:
3511                    x = left + (nth + 1) * self._width / (no_arcs + 1.0)
3512                else:
3513                    x = self._xpos
3514                y = bottom
3515                return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos)
3516            elif physicalAttachment == 3:
3517                x = left
3518                if self._spaceAttachments:
3519                    y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
3520                else:
3521                    y = self._ypos
3522                return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y)
3523            else:
3524                return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line)
3525        else:
3526            return self._xpos, self._ypos
3527
3528
3529class CircleShape(EllipseShape):
3530    """
3531    The :class:`CircleShape` class is an :class:`EllipseShape` whose width
3532    and height are the same.
3533    """
3534    def __init__(self, diameter):
3535        """
3536        Default class constructor
3537
3538        :param `diameter`: the diameter
3539
3540        """
3541        EllipseShape.__init__(self, diameter, diameter)
3542        self.SetMaintainAspectRatio(True)
3543
3544    def GetPerimeterPoint(self, x1, y1, x2, y2):
3545        """
3546        Get the perimeter point.
3547
3548        :param `x1`: ???
3549        :param `y1`: ???
3550        :param `x2`: ???
3551        :param `y2`: ???
3552        :returns: ???
3553
3554        """
3555        return FindEndForCircle(self._width / 2.0, self._xpos, self._ypos, x2, y2)
3556
3557
3558class TextShape(RectangleShape):
3559    """
3560    The :class:`TextShape` class like :class:`wx.RectangleShape` but only the
3561    text is displayed.
3562    """
3563    def __init__(self, width, height):
3564        """
3565        Default class constructor
3566
3567        :param `width`: the width
3568        :param `height`: the height
3569
3570        """
3571        RectangleShape.__init__(self, width, height)
3572
3573    def OnDraw(self, dc):
3574        """not implemented???"""
3575        pass
3576
3577
3578class ShapeRegion(object):
3579    """The :class:`ShapeRegion` class."""
3580    def __init__(self, region = None):
3581        """
3582        Default class constructor
3583
3584        :param `region`: a parent region or None???
3585
3586        """
3587        if region:
3588            self._regionText = region._regionText
3589            self._regionName = region._regionName
3590            self._textColour = region._textColour
3591
3592            self._font = region._font
3593            self._minHeight = region._minHeight
3594            self._minWidth = region._minWidth
3595            self._width = region._width
3596            self._height = region._height
3597            self._x = region._x
3598            self._y = region._y
3599
3600            self._regionProportionX = region._regionProportionX
3601            self._regionProportionY = region._regionProportionY
3602            self._formatMode = region._formatMode
3603            self._actualColourObject = region._actualColourObject
3604            self._actualPenObject = None
3605            self._penStyle = region._penStyle
3606            self._penColour = region._penColour
3607
3608            self.ClearText()
3609            for line in region._formattedText:
3610                new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText())
3611                self._formattedText.append(new_line)
3612        else:
3613            self._regionText = ""
3614            self._font = NormalFont
3615            self._minHeight = 5.0
3616            self._minWidth = 5.0
3617            self._width = 0.0
3618            self._height = 0.0
3619            self._x = 0.0
3620            self._y = 0.0
3621
3622            self._regionProportionX = -1.0
3623            self._regionProportionY = -1.0
3624            self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
3625            self._regionName = ""
3626            self._textColour = "BLACK"
3627            self._penColour = "BLACK"
3628            self._penStyle = wx.PENSTYLE_SOLID
3629            self._actualColourObject = wx.TheColourDatabase.Find("BLACK")
3630            self._actualPenObject = None
3631
3632            self._formattedText = []
3633
3634    def ClearText(self):
3635        """Clear the text."""
3636        self._formattedText = []
3637
3638    def SetFont(self, f):
3639        """
3640        Set the font.
3641
3642        :param `f`: an instance of :class:`wx.Font`
3643        """
3644        self._font = f
3645
3646    def SetMinSize(self, w, h):
3647        """
3648        Set the minumum size.
3649
3650        :param `w`: the minimum width
3651        :Param `h`: the minimum height
3652
3653        """
3654        self._minWidth = w
3655        self._minHeight = h
3656
3657    def SetSize(self, w, h):
3658        """
3659        Set the size.
3660
3661        :param `w`: the width
3662        :Param `h`: the jeight
3663
3664        """
3665        self._width = w
3666        self._height = h
3667
3668    def SetPosition(self, xp, yp):
3669        """
3670        Set the position.
3671
3672        :param `xp`: the x position
3673        :Param `yp`: the y position
3674
3675        """
3676        self._x = xp
3677        self._y = yp
3678
3679    def SetProportions(self, xp, yp):
3680        """
3681        Set the proportions.
3682
3683        :param `xp`: the x region proportion
3684        :Param `yp`: the y region proportion
3685
3686        """
3687        self._regionProportionX = xp
3688        self._regionProportionY = yp
3689
3690    def SetFormatMode(self, mode):
3691        """
3692        Set the format mode of the region.
3693
3694        :param `mode`: can be a bit list of the following
3695
3696        ============================== ==============================
3697        Format mode                    Description
3698        ============================== ==============================
3699        `FORMAT_NONE`                  No formatting
3700        `FORMAT_CENTRE_HORIZ`          Horizontal centring
3701        `FORMAT_CENTRE_VERT`           Vertical centring
3702        ============================== ==============================
3703
3704        """
3705        self._formatMode = mode
3706
3707    def SetColour(self, col):
3708        """
3709        Set the colour.
3710
3711        :param str `col`: a valid colour name,
3712         see :class:`wx.ColourDatabase`
3713
3714        """
3715        self._textColour = col
3716        self._actualColourObject = col
3717
3718    def GetActualColourObject(self):
3719        """Get the actual colour object from the :class:`wx.ColourDatabase`."""
3720        self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour())
3721        return self._actualColourObject
3722
3723    def SetPenColour(self, col):
3724        """
3725        Set the pen colour.
3726
3727        :param str `col`: a valid colour name,
3728         see :class:`wx.ColourDatabase`
3729
3730        """
3731        self._penColour = col
3732        self._actualPenObject = None
3733
3734    def GetActualPen(self):
3735        """
3736        Get actual pen.
3737
3738        :note: Returns NULL if the pen is invisible
3739         (different to pen being transparent; indicates that
3740         region boundary should not be drawn.)
3741
3742         """
3743        if self._actualPenObject:
3744            return self._actualPenObject
3745
3746        if not self._penColour:
3747            return None
3748        if self._penColour=="Invisible":
3749            return None
3750        self._actualPenObject = wx.Pen(self._penColour, 1, self._penStyle)
3751        return self._actualPenObject
3752
3753    def SetText(self, s):
3754        """
3755        Set the text.
3756
3757        :param str `s`: the text
3758
3759        """
3760        self._regionText = s
3761
3762    def SetName(self, s):
3763        """
3764        Set the name.
3765
3766        :param str `s`: the name
3767
3768        """
3769        self._regionName = s
3770
3771    def GetText(self):
3772        """Get the text."""
3773        return self._regionText
3774
3775    def GetFont(self):
3776        """Get the font."""
3777        return self._font
3778
3779    def GetMinSize(self):
3780        """Get the minimum size."""
3781        return self._minWidth, self._minHeight
3782
3783    def GetProportion(self):
3784        """Get the proportion."""
3785        return self._regionProportionX, self._regionProportionY
3786
3787    def GetSize(self):
3788        """Get the size."""
3789        return self._width, self._height
3790
3791    def GetPosition(self):
3792        """Get the position."""
3793        return self._x, self._y
3794
3795    def GetFormatMode(self):
3796        """Get the format mode."""
3797        return self._formatMode
3798
3799    def GetName(self):
3800        """Get the name."""
3801        return self._regionName
3802
3803    def GetColour(self):
3804        """Get the colour."""
3805        return self._textColour
3806
3807    def GetFormattedText(self):
3808        """Get the formatted text."""
3809        return self._formattedText
3810
3811    def GetPenColour(self):
3812        """Get the pen colour"""
3813        return self._penColour
3814
3815    def GetPenStyle(self):
3816        """Get the pen style."""
3817        return self._penStyle
3818
3819    def SetPenStyle(self, style):
3820        """
3821        Set the pen style.
3822
3823        :param `style`: the style, see :class:`wx.Pen`
3824
3825        """
3826        self._penStyle = style
3827        self._actualPenObject = None
3828
3829    def GetWidth(self):
3830        """Get the width."""
3831        return self._width
3832
3833    def GetHeight(self):
3834        """Get the height."""
3835        return self._height
3836
3837
3838class ControlPoint(RectangleShape):
3839    """The :class:`wx.ControlPoint` class."""
3840    def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type):
3841        """
3842        Default class constructor
3843
3844        :param `theCanvas`: a :class:`~lib.ogl.Canvas`
3845        :param `object`: the shape, instance of :class:`~lib.ogl.Shape`
3846        :param float `size`: the size
3847        :param float `the_xoffset`: the x position
3848        :param float `the_yoffset`: the y position
3849        :param int `the_type`: one of the following types ???
3850
3851         ======================================== ==================================
3852         Control point type                       Description
3853         ======================================== ==================================
3854         `CONTROL_POINT_VERTICAL`                 Vertical
3855         `CONTROL_POINT_HORIZONTAL`               Horizontal
3856         `CONTROL_POINT_DIAGONAL`                 Diagonal
3857         ======================================== ==================================
3858
3859        """
3860        RectangleShape.__init__(self, size, size)
3861
3862        self._canvas = theCanvas
3863        self._shape = object
3864        self._xoffset = the_xoffset
3865        self._yoffset = the_yoffset
3866        self._type = the_type
3867        self.SetPen(BlackForegroundPen)
3868        self.SetBrush(wx.BLACK_BRUSH)
3869        self._oldCursor = None
3870        self._visible = True
3871        self._eraseObject = True
3872
3873    # Don't even attempt to draw any text - waste of time
3874    def OnDrawContents(self, dc):
3875        """not implemented???"""
3876        pass
3877
3878    def OnDraw(self, dc):
3879        """The draw handler."""
3880        self._xpos = self._shape.GetX() + self._xoffset
3881        self._ypos = self._shape.GetY() + self._yoffset
3882        RectangleShape.OnDraw(self, dc)
3883
3884    def OnErase(self, dc):
3885        """The erase handler."""
3886        RectangleShape.OnErase(self, dc)
3887
3888    # Implement resizing of canvas object
3889    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
3890        """The drag left handler."""
3891        self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
3892
3893    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3894        """The begin drag left handler."""
3895        self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3896
3897    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3898        """The end drag left handler."""
3899        self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3900
3901    def GetNumberOfAttachments(self):
3902        """Get the number of attachments."""
3903        return 1
3904
3905    def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
3906        """
3907        Get the attachment position.
3908
3909        :param `attachment`: the attachment ???
3910        :param `nth`: get nth attachment ???
3911        :param `no_arcs`: ???
3912        :param `line`: ???
3913
3914        """
3915        return self._xpos, self._ypos
3916
3917    def SetEraseObject(self, er):
3918        """
3919        Set the erase object ???
3920
3921        :param `er`: the object
3922
3923        """
3924        self._eraseObject = er
3925
3926
3927class PolygonControlPoint(ControlPoint):
3928    """The :class:`PolygonControlPoint` class."""
3929    def __init__(self, theCanvas, object, size, vertex, the_xoffset, the_yoffset):
3930        """
3931        Default class constructor
3932
3933        :param `theCanvas`: a :class:`~lib.ogl.Canvas`
3934        :param `object`: the shape, instance of :class:`~lib.ogl.Shape`
3935        :param float `size`: the size
3936        :param `vertext`: the vertex
3937        :param float `the_xoffset`: the x position
3938        :param float `the_yoffset`: the y position
3939        :param int `the_type`: one of the following types ???
3940
3941         ======================================== ==================================
3942         Control point type                       Description
3943         ======================================== ==================================
3944         `CONTROL_POINT_VERTICAL`                 Vertical
3945         `CONTROL_POINT_HORIZONTAL`               Horizontal
3946         `CONTROL_POINT_DIAGONAL`                 Diagonal
3947         ======================================== ==================================
3948
3949        """
3950        ControlPoint.__init__(self, theCanvas, object, size, the_xoffset, the_yoffset, 0)
3951        self._polygonVertex = vertex
3952        self._originalDistance = 0.0
3953        self._newSize = wx.RealPoint()
3954        self._originalSize = wx.RealPoint()
3955
3956    def GetNewSize(self):
3957        """Get the new size."""
3958        return self._newSize
3959
3960    def CalculateNewSize(self, x, y):
3961        """
3962        Calculate what new size would be, at end of resize.
3963
3964        :param `x`: x ???
3965        :param `y`: y ???
3966
3967        """
3968        bound_x, bound_y = self.GetShape().GetBoundingBoxMax()
3969        dist = math.sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY()))
3970
3971        self._newSize[0] = dist / self._originalDistance * self._originalSize[0]
3972        self._newSize[1] = dist / self._originalDistance * self._originalSize[1]
3973
3974    # Implement resizing polygon or moving the vertex
3975    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
3976        """The drag left handler."""
3977        self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
3978
3979    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3980        """The begin drag left handler."""
3981        self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3982
3983    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3984        """The end drag left handler."""
3985        self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3986
3987from .canvas import *
3988from .lines import *
3989from .composit import *
3990