~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

« back to all changes in this revision

Viewing changes to wxPython/wx/lib/ogl/_basic.py

  • Committer: Brian Sidebotham
  • Date: 2013-08-03 14:30:08 UTC
  • Revision ID: brian.sidebotham@gmail.com-20130803143008-c7806tkych1tp6fc
Initial import into Bazaar

Show diffs side-by-side

added added

removed removed

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