1
# -*- coding: utf-8 -*-
2
#----------------------------------------------------------------------------
4
# Purpose: The basic OGL shapes
6
# Author: Pierre Hjälm (from C++ original by Julian Smart)
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
#----------------------------------------------------------------------------
17
from _oglmisc import *
23
global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen
24
global BlackForegroundPen, NormalFont
26
WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.SOLID)
27
WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.SOLID)
29
TransparentPen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)
30
BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.SOLID)
32
NormalFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
39
class ShapeTextLine(object):
40
def __init__(self, the_x, the_y, the_line):
57
def SetText(self, text):
65
class ShapeEvtHandler(object):
66
def __init__(self, prev = None, shape = None):
67
self._previousHandler = prev
68
self._handlerShape = shape
70
def SetShape(self, sh):
71
self._handlerShape = sh
74
return self._handlerShape
76
def SetPreviousHandler(self, handler):
77
self._previousHandler = handler
79
def GetPreviousHandler(self):
80
return self._previousHandler
83
if self!=self.GetShape():
87
if self._previousHandler:
88
self._previousHandler.OnDraw(dc)
90
def OnMoveLinks(self, dc):
91
if self._previousHandler:
92
self._previousHandler.OnMoveLinks(dc)
94
def OnMoveLink(self, dc, moveControlPoints = True):
95
if self._previousHandler:
96
self._previousHandler.OnMoveLink(dc, moveControlPoints)
98
def OnDrawContents(self, dc):
99
if self._previousHandler:
100
self._previousHandler.OnDrawContents(dc)
102
def OnDrawBranches(self, dc, erase = False):
103
if self._previousHandler:
104
self._previousHandler.OnDrawBranches(dc, erase = erase)
106
def OnSize(self, x, y):
107
if self._previousHandler:
108
self._previousHandler.OnSize(x, y)
110
def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
111
if self._previousHandler:
112
return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display)
116
def OnMovePost(self, dc, x, y, old_x, old_y, display = True):
117
if self._previousHandler:
118
return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display)
122
def OnErase(self, dc):
123
if self._previousHandler:
124
self._previousHandler.OnErase(dc)
126
def OnEraseContents(self, dc):
127
if self._previousHandler:
128
self._previousHandler.OnEraseContents(dc)
130
def OnHighlight(self, dc):
131
if self._previousHandler:
132
self._previousHandler.OnHighlight(dc)
134
def OnLeftClick(self, x, y, keys, attachment):
135
if self._previousHandler:
136
self._previousHandler.OnLeftClick(x, y, keys, attachment)
138
def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
139
if self._previousHandler:
140
self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment)
142
def OnRightClick(self, x, y, keys = 0, attachment = 0):
143
if self._previousHandler:
144
self._previousHandler.OnRightClick(x, y, keys, attachment)
146
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
147
if self._previousHandler:
148
self._previousHandler.OnDragLeft(draw, x, y, keys, attachment)
150
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
151
if self._previousHandler:
152
self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
154
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
155
if self._previousHandler:
156
self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
158
def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
159
if self._previousHandler:
160
self._previousHandler.OnDragRight(draw, x, y, keys, attachment)
162
def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
163
if self._previousHandler:
164
self._previousHandler.OnBeginDragRight(x, y, keys, attachment)
166
def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
167
if self._previousHandler:
168
self._previousHandler.OnEndDragRight(x, y, keys, attachment)
170
# Control points ('handles') redirect control to the actual shape,
171
# to make it easier to override sizing behaviour.
172
def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
173
if self._previousHandler:
174
self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment)
176
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
177
if self._previousHandler:
178
self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment)
180
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
181
if self._previousHandler:
182
self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment)
184
def OnBeginSize(self, w, h):
187
def OnEndSize(self, w, h):
190
def OnDrawOutline(self, dc, x, y, w, h):
191
if self._previousHandler:
192
self._previousHandler.OnDrawOutline(dc, x, y, w, h)
194
def OnDrawControlPoints(self, dc):
195
if self._previousHandler:
196
self._previousHandler.OnDrawControlPoints(dc)
198
def OnEraseControlPoints(self, dc):
199
if self._previousHandler:
200
self._previousHandler.OnEraseControlPoints(dc)
202
# Can override this to prevent or intercept line reordering.
203
def OnChangeAttachment(self, attachment, line, ordering):
204
if self._previousHandler:
205
self._previousHandler.OnChangeAttachment(attachment, line, ordering)
209
class Shape(ShapeEvtHandler):
214
The wxShape is the top-level, abstract object that all other objects
215
are derived from. All common functionality is represented by wxShape's
216
members, and overriden members that appear in derived classes and have
217
behaviour as documented for wxShape, are not documented separately.
220
GraphicsInSizeToContents = False
222
def __init__(self, canvas = None):
223
ShapeEvtHandler.__init__(self)
225
self._eventHandler = self
228
self._formatted = False
229
self._canvas = canvas
232
self._pen = 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
248
self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
249
self._shadowMode = SHADOW_NONE
250
self._shadowOffsetX = 6
251
self._shadowOffsetY = 6
252
self._shadowBrush = wx.BLACK_BRUSH
253
self._textMarginX = 5
254
self._textMarginY = 5
255
self._regionName = "0"
256
self._centreResize = True
257
self._maintainAspectRatio = False
258
self._highlighted = False
260
self._branchNeckLength = 10
261
self._branchStemLength = 10
262
self._branchSpacing = 10
263
self._branchStyle = BRANCHING_ATTACHMENT_NORMAL
267
self._controlPoints = []
268
self._attachmentPoints = []
272
# Set up a default region. Much of the above will be put into
273
# the region eventually (the duplication is for compatibility)
274
region = ShapeRegion()
276
region.SetFont(NormalFont)
277
region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT)
278
region.SetColour("BLACK")
279
self._regions.append(region)
282
return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
284
def GetClassName(self):
285
return str(self.__class__).split(".")[-1][:-2]
289
Fully disconnect this shape from parents, children, the
293
self._parent.GetChildren().remove(self)
295
for child in self.GetChildren():
300
self.ClearAttachments()
302
self._handlerShape = None
305
self.RemoveFromCanvas(self._canvas)
307
if self.GetEventHandler():
308
self.GetEventHandler().OnDelete()
309
self._eventHandler = None
312
"""TRUE if the shape may be dragged by the user."""
315
def SetShape(self, sh):
316
self._handlerShape = sh
319
"""Get the internal canvas."""
322
def GetBranchStyle(self):
323
return self._branchStyle
325
def GetRotation(self):
326
"""Return the angle of rotation in radians."""
327
return self._rotation
329
def SetRotation(self, rotation):
330
self._rotation = rotation
332
def SetHighlight(self, hi, recurse = False):
333
"""Set the highlight for a shape. Shape highlighting is unimplemented."""
334
self._highlighted = hi
336
for shape in self._children:
337
shape.SetHighlight(hi, recurse)
339
def SetSensitivityFilter(self, sens = OP_ALL, recursive = False):
340
"""Set the shape to be sensitive or insensitive to specific mouse
343
sens is a bitlist of the following:
349
* OP_ALL (equivalent to a combination of all the above).
351
self._draggable = sens & OP_DRAG_LEFT
353
self._sensitivity = sens
355
for shape in self._children:
356
shape.SetSensitivityFilter(sens, True)
358
def SetDraggable(self, drag, recursive = False):
359
"""Set the shape to be draggable or not draggable."""
360
self._draggable = drag
362
self._sensitivity |= OP_DRAG_LEFT
363
elif self._sensitivity & OP_DRAG_LEFT:
364
self._sensitivity -= OP_DRAG_LEFT
367
for shape in self._children:
368
shape.SetDraggable(drag, True)
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.
375
self._drawHandles = drawH
376
for shape in self._children:
377
shape.SetDrawHandles(drawH)
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:
384
No shadow (the default).
386
Shadow on the left side.
388
Shadow on the right side.
390
if redraw and self.GetCanvas():
391
dc = wx.ClientDC(self.GetCanvas())
392
self.GetCanvas().PrepareDC(dc)
394
self._shadowMode = mode
397
self._shadowMode = mode
399
def GetShadowMode(self):
400
"""Return the current shadow mode setting"""
401
return self._shadowMode
403
def SetCanvas(self, theCanvas):
404
"""Identical to Shape.Attach."""
405
self._canvas = theCanvas
406
for shape in self._children:
407
shape.SetCanvas(theCanvas)
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.
413
theCanvas.AddShape(self, addAfter)
416
for object in self._children:
417
object.AddToCanvas(theCanvas, lastImage)
420
def InsertInCanvas(self, theCanvas):
421
"""Insert the shape at the front of the shape list of canvas."""
422
theCanvas.InsertShape(self)
425
for object in self._children:
426
object.AddToCanvas(theCanvas, lastImage)
429
def RemoveFromCanvas(self, theCanvas):
430
"""Remove the shape from the canvas."""
435
theCanvas.RemoveShape(self)
436
for object in self._children:
437
object.RemoveFromCanvas(theCanvas)
439
def ClearAttachments(self):
440
"""Clear internal custom attachment point shapes (of class
443
self._attachmentPoints = []
445
def ClearText(self, regionId = 0):
446
"""Clear the text from the specified text region."""
449
if regionId < len(self._regions):
450
self._regions[regionId].ClearText()
452
def ClearRegions(self):
453
"""Clear the ShapeRegions from the shape."""
456
def AddRegion(self, region):
457
"""Add a region to the shape."""
458
self._regions.append(region)
460
def SetDefaultRegionSize(self):
461
"""Set the default region to be consistent with the shape size."""
462
if not self._regions:
464
w, h = self.GetBoundingBoxMax()
465
self._regions[0].SetSize(w, h)
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.
472
width, height = self.GetBoundingBoxMax()
478
width += 4 # Allowance for inaccurate mousing
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
486
nearest_attachment = 0
488
# If within the bounding box, check the attachment points
490
if x >= left and x <= right and y >= top and y <= bottom:
491
n = self.GetNumberOfAttachments()
494
# GetAttachmentPosition[Edge] takes a logical attachment position,
495
# i.e. if it's rotated through 90%, position 0 is East-facing.
498
e = self.GetAttachmentPositionEdge(i)
501
l = math.sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y))
504
nearest_attachment = i
506
return nearest_attachment, nearest
509
# Format a text string according to the region size, adding
510
# strings with positions to region text list
512
def FormatText(self, dc, s, i = 0):
513
"""Reformat the given text region; defaults to formatting the
518
if not self._regions:
521
if i >= len(self._regions):
524
region = self._regions[i]
525
region._regionText = s
526
dc.SetFont(region.GetFont())
528
w, h = region.GetSize()
530
stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode())
532
line = ShapeTextLine(0.0, 0.0, s)
533
region.GetFormattedText().append(line)
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:
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
549
topAncestor = self.GetTopAncestor()
550
if topAncestor != self:
551
Shape.GraphicsInSizeToContents = True
553
composite = topAncestor
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
564
Shape.GraphicsInSizeToContents = False
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
574
def Recentre(self, dc):
575
"""Do recentring (or other formatting) for all the text regions
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())
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.
588
def SetPen(self, the_pen):
589
"""Set the pen for drawing the shape's outline."""
592
def SetBrush(self, the_brush):
593
"""Set the brush for filling the shape's shape."""
594
self._brush = the_brush
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
601
if not self.GetParent():
604
if isinstance(self.GetParent(), DivisionShape):
606
return self.GetParent().GetTopAncestor()
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)
615
def GetFont(self, regionId = 0):
616
"""Get the font for the specified text region."""
617
if regionId >= len(self._regions):
619
return self._regions[regionId].GetFont()
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:
632
if regionId < len(self._regions):
633
self._regions[regionId].SetFormatMode(mode)
635
def GetFormatMode(self, regionId = 0):
636
if regionId >= len(self._regions):
638
return self._regions[regionId].GetFormatMode()
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
645
if regionId < len(self._regions):
646
self._regions[regionId].SetColour(the_colour)
648
def GetTextColour(self, regionId = 0):
649
"""Get the colour for the specified text region."""
650
if regionId >= len(self._regions):
652
return self._regions[regionId].GetColour()
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.
659
if regionId < len(self._regions):
660
self._regions[regionId].SetName(name)
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.
667
if regionId >= len(self._regions):
669
return self._regions[regionId].GetName()
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
676
for i, r in enumerate(self._regions):
677
if r.GetName() == name:
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()
687
buff = parentName+"."+str(i)
690
self.SetRegionName(buff, i)
692
for j, child in enumerate(self._children):
694
buff = parentName+"."+str(j)
697
child.NameRegions(buff)
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.
704
id = self.GetRegionId(name)
708
for child in self._children:
709
actualImage, regionId = child.FindRegion(name)
711
return actualImage, regionId
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)."""
719
n = self.GetNumberOfTextRegions()
721
list.append(self.GetRegionName(i))
723
for child in self._children:
724
list += child.FindRegionNames()
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:
734
def OnDraw(self, dc):
737
def OnMoveLinks(self, dc):
738
# Want to set the ends of all attached links
739
# to point to / from this object
741
for line in self._lines:
742
line.GetEventHandler().OnMoveLink(dc)
744
def OnDrawContents(self, dc):
745
if not self._regions:
748
bound_x, bound_y = self.GetBoundingBoxMin()
753
for region in self._regions:
755
dc.SetFont(region.GetFont())
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
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())
767
def DrawContents(self, dc):
768
"""Draw the internal graphic of the shape (such as text).
770
Do not override this function: override OnDrawContents, which
771
is called by this function.
773
self.GetEventHandler().OnDrawContents(dc)
775
def OnSize(self, x, y):
778
def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
781
def OnErase(self, dc):
782
if not self._visible:
786
for line in self._lines:
787
line.GetEventHandler().OnErase(dc)
789
self.GetEventHandler().OnEraseContents(dc)
791
def OnEraseContents(self, dc):
792
if not self._visible:
795
xp, yp = self.GetX(), self.GetY()
796
minX, minY = self.GetBoundingBoxMin()
797
maxX, maxY = self.GetBoundingBoxMax()
799
topLeftX = xp - maxX / 2.0 - 2
800
topLeftY = yp - maxY / 2.0 - 2
804
penWidth = self._pen.GetWidth()
806
dc.SetPen(self.GetBackgroundPen())
807
dc.SetBrush(self.GetBackgroundBrush())
809
dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4)
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.
815
if not self._visible:
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)
823
for child in self._children:
824
child.EraseLinks(dc, attachment, recurse)
826
def DrawLinks(self, dc, attachment = -1, recurse = False):
827
"""Draws any lines linked to this shape."""
828
if not self._visible:
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):
836
for child in self._children:
837
child.DrawLinks(dc, attachment, recurse)
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.
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.
848
attachment is the attachment point (side) in question.
850
This function is used in Shape.MoveLineToNewAttachment to determine
851
the new line ordering.
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]
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.
866
Calls Shape.AttachmentSortTest and then
867
ShapeEvtHandler.OnChangeAttachment.
869
if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
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)
878
newAttachment, distance = hit
882
if to_move.GetTo() == self:
883
oldAttachment = to_move.GetAttachmentTo()
885
oldAttachment = to_move.GetAttachmentFrom()
887
# The links in a new ordering
888
# First, add all links to the new list
889
newOrdering = self._lines[:]
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)]
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:
911
thisPoint = wx.RealPoint(xp, yp)
912
lastPoint = wx.RealPoint(old_x, old_y)
913
newPoint = wx.RealPoint(x, y)
915
if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint):
917
newOrdering.insert(newOrdering.index(line), to_move)
925
newOrdering.append(to_move)
927
self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering)
930
def OnChangeAttachment(self, attachment, line, ordering):
931
if line.GetTo() == self:
932
line.SetAttachmentTo(attachment)
934
line.SetAttachmentFrom(attachment)
936
self.ApplyAttachmentOrdering(ordering)
938
dc = wx.ClientDC(self.GetCanvas())
939
self.GetCanvas().PrepareDC(dc)
942
if not self.GetCanvas().GetQuickEditMode():
943
self.GetCanvas().Redraw(dc)
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.
950
linesStore = self._lines[:]
954
for line in linesToSort:
955
if line in linesStore:
956
del linesStore[linesStore.index(line)]
957
self._lines.append(line)
959
# Now add any lines that haven't been listed in linesToSort
960
self._lines += linesStore
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.
966
Any remaining lines not in the list will be added to the end.
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 = []
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)]
978
for line in linesToSort:
979
if line in linesAtThisAttachment:
981
del linesAtThisAttachment[linesAtThisAttachment.index(line)]
982
self._lines.append(line)
984
# Now add any lines that haven't been listed in linesToSort
985
self._lines += linesAtThisAttachment
987
def OnHighlight(self, dc):
990
def OnLeftClick(self, x, y, keys = 0, attachment = 0):
991
if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT:
993
attachment, dist = self._parent.HitTest(x, y)
994
self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment)
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)
1001
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1002
if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1004
hit = self._parent.HitTest(x, y)
1006
attachment, dist = hit
1007
self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
1010
dc = wx.ClientDC(self.GetCanvas())
1011
self.GetCanvas().PrepareDC(dc)
1012
dc.SetLogicalFunction(OGLRBLF)
1014
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1015
dc.SetPen(dottedPen)
1016
dc.SetBrush(wx.TRANSPARENT_BRUSH)
1018
xx = x + DragOffsetX
1019
yy = y + DragOffsetY
1021
xx, yy = self._canvas.Snap(xx, yy)
1022
w, h = self.GetBoundingBoxMax()
1023
self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1025
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1026
global DragOffsetX, DragOffsetY
1028
if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1030
hit = self._parent.HitTest(x, y)
1032
attachment, dist = hit
1033
self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
1036
DragOffsetX = self._xpos - x
1037
DragOffsetY = self._ypos - y
1039
dc = wx.ClientDC(self.GetCanvas())
1040
self.GetCanvas().PrepareDC(dc)
1042
# New policy: don't erase shape until end of drag.
1044
xx = x + DragOffsetX
1045
yy = y + DragOffsetY
1046
xx, yy = self._canvas.Snap(xx, yy)
1047
dc.SetLogicalFunction(OGLRBLF)
1049
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1050
dc.SetPen(dottedPen)
1051
dc.SetBrush(wx.TRANSPARENT_BRUSH)
1053
w, h = self.GetBoundingBoxMax()
1054
self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1055
self._canvas.CaptureMouse()
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:
1062
hit = self._parent.HitTest(x, y)
1064
attachment, dist = hit
1065
self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
1068
dc = wx.ClientDC(self.GetCanvas())
1069
self.GetCanvas().PrepareDC(dc)
1071
dc.SetLogicalFunction(wx.COPY)
1072
xx = x + DragOffsetX
1073
yy = y + DragOffsetY
1074
xx, yy = self._canvas.Snap(xx, yy)
1076
# New policy: erase shape at end of drag.
1079
self.Move(dc, xx, yy)
1080
if self._canvas and not self._canvas.GetQuickEditMode():
1081
self._canvas.Redraw(dc)
1083
def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
1084
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1086
attachment, dist = self._parent.HitTest(x, y)
1087
self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment)
1090
def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
1091
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1093
attachment, dist = self._parent.HitTest(x, y)
1094
self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment)
1097
def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
1098
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1100
attachment, dist = self._parent.HitTest(x, y)
1101
self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment)
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],
1112
dc.DrawLines(points)
1114
def Attach(self, can):
1115
"""Set the shape's internal canvas pointer to point to the given canvas."""
1119
"""Disassociates the shape from its canvas."""
1122
def Move(self, dc, x, y, display = True):
1123
"""Move the shape to the given position.
1124
Redraw if display is TRUE.
1129
if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display):
1132
self._xpos, self._ypos = x, y
1134
self.ResetControlPoints()
1141
self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display)
1143
def MoveLinks(self, dc):
1144
"""Redraw all the lines attached to the shape."""
1145
self.GetEventHandler().OnMoveLinks(dc)
1148
"""Draw the whole shape and any lines attached to it.
1150
Do not override this function: override OnDraw, which is called
1154
self.GetEventHandler().OnDraw(dc)
1155
self.GetEventHandler().OnDrawContents(dc)
1156
self.GetEventHandler().OnDrawControlPoints(dc)
1157
self.GetEventHandler().OnDrawBranches(dc)
1160
"""Flash the shape."""
1161
if self.GetCanvas():
1162
dc = wx.ClientDC(self.GetCanvas())
1163
self.GetCanvas().PrepareDC(dc)
1165
dc.SetLogicalFunction(OGLRBLF)
1167
dc.SetLogicalFunction(wx.COPY)
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:
1176
def Erase(self, dc):
1178
Does not repair damage caused to other shapes.
1180
self.GetEventHandler().OnErase(dc)
1181
self.GetEventHandler().OnEraseControlPoints(dc)
1182
self.GetEventHandler().OnDrawBranches(dc, erase = True)
1184
def EraseContents(self, dc):
1185
"""Erase the shape contents, that is, the area within the shape's
1186
minimum bounding box.
1188
self.GetEventHandler().OnEraseContents(dc)
1190
def AddText(self, string):
1191
"""Add a line of text to the shape's default text region."""
1192
if not self._regions:
1195
region = self._regions[0]
1197
new_line = ShapeTextLine(0, 0, string)
1198
text = region.GetFormattedText()
1199
text.append(new_line)
1201
self._formatted = False
1203
def SetSize(self, x, y, recursive = True):
1204
"""Set the shape's size."""
1205
self.SetAttachmentSize(x, y)
1206
self.SetDefaultRegionSize()
1208
def SetAttachmentSize(self, w, h):
1209
width, height = self.GetBoundingBoxMin()
1213
scaleX = float(w) / width
1217
scaleY = float(h) / height
1219
for point in self._attachmentPoints:
1220
point._x = point._x * scaleX
1221
point._y = point._y * scaleY
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.
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
1232
if positionFrom == -1:
1233
if not line in self._lines:
1234
self._lines.append(line)
1236
# Don't preserve old ordering if we have new ordering instructions
1238
self._lines.remove(line)
1241
if positionFrom < len(self._lines):
1242
self._lines.insert(positionFrom, line)
1244
self._lines.append(line)
1246
if positionTo == -1:
1247
if not other in other._lines:
1248
other._lines.append(line)
1250
# Don't preserve old ordering if we have new ordering instructions
1252
other._lines.remove(line)
1255
if positionTo < len(other._lines):
1256
other._lines.insert(positionTo, line)
1258
other._lines.append(line)
1262
line.SetAttachments(attachFrom, attachTo)
1264
dc = wx.ClientDC(self._canvas)
1265
self._canvas.PrepareDC(dc)
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)
1273
line.GetFrom()._lines.remove(line)
1275
self._lines.remove(line)
1277
# Default - make 6 control points
1278
def MakeControlPoints(self):
1279
"""Make a list of control points (draggable handles) appropriate to
1282
maxX, maxY = self.GetBoundingBoxMax()
1283
minX, minY = self.GetBoundingBoxMin()
1285
widthMin = minX + CONTROL_POINT_SIZE + 2
1286
heightMin = minY + CONTROL_POINT_SIZE + 2
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)
1294
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL)
1295
self._canvas.AddShape(control)
1296
self._controlPoints.append(control)
1298
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL)
1299
self._canvas.AddShape(control)
1300
self._controlPoints.append(control)
1302
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL)
1303
self._canvas.AddShape(control)
1304
self._controlPoints.append(control)
1306
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL)
1307
self._canvas.AddShape(control)
1308
self._controlPoints.append(control)
1310
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL)
1311
self._canvas.AddShape(control)
1312
self._controlPoints.append(control)
1314
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL)
1315
self._canvas.AddShape(control)
1316
self._controlPoints.append(control)
1318
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL)
1319
self._canvas.AddShape(control)
1320
self._controlPoints.append(control)
1322
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL)
1323
self._canvas.AddShape(control)
1324
self._controlPoints.append(control)
1326
def MakeMandatoryControlPoints(self):
1327
"""Make the mandatory control points.
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).
1333
for child in self._children:
1334
child.MakeMandatoryControlPoints()
1336
def ResetMandatoryControlPoints(self):
1337
"""Reset the mandatory control points."""
1338
for child in self._children:
1339
child.ResetMandatoryControlPoints()
1341
def ResetControlPoints(self):
1342
"""Reset the positions of the control points (for instance when the
1343
shape's shape has changed).
1345
self.ResetMandatoryControlPoints()
1347
if len(self._controlPoints) == 0:
1350
maxX, maxY = self.GetBoundingBoxMax()
1351
minX, minY = self.GetBoundingBoxMin()
1353
widthMin = minX + CONTROL_POINT_SIZE + 2
1354
heightMin = minY + CONTROL_POINT_SIZE + 2
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)
1362
self._controlPoints[0]._xoffset = left
1363
self._controlPoints[0]._yoffset = top
1365
self._controlPoints[1]._xoffset = 0
1366
self._controlPoints[1]._yoffset = top
1368
self._controlPoints[2]._xoffset = right
1369
self._controlPoints[2]._yoffset = top
1371
self._controlPoints[3]._xoffset = right
1372
self._controlPoints[3]._yoffset = 0
1374
self._controlPoints[4]._xoffset = right
1375
self._controlPoints[4]._yoffset = bottom
1377
self._controlPoints[5]._xoffset = 0
1378
self._controlPoints[5]._yoffset = bottom
1380
self._controlPoints[6]._xoffset = left
1381
self._controlPoints[6]._yoffset = bottom
1383
self._controlPoints[7]._xoffset = left
1384
self._controlPoints[7]._yoffset = 0
1386
def DeleteControlPoints(self, dc = None):
1387
"""Delete the control points (or handles) for the shape.
1389
Does not redraw the shape.
1391
for control in self._controlPoints[:]:
1393
control.GetEventHandler().OnErase(dc)
1395
self._controlPoints.remove(control)
1396
self._controlPoints = []
1398
# Children of divisions are contained objects,
1400
if not isinstance(self, DivisionShape):
1401
for child in self._children:
1402
child.DeleteControlPoints(dc)
1404
def OnDrawControlPoints(self, dc):
1405
if not self._drawHandles:
1408
dc.SetBrush(wx.BLACK_BRUSH)
1409
dc.SetPen(wx.BLACK_PEN)
1411
for control in self._controlPoints:
1414
# Children of divisions are contained objects,
1416
# This test bypasses the type facility for speed
1417
# (critical when drawing)
1419
if not isinstance(self, DivisionShape):
1420
for child in self._children:
1421
child.GetEventHandler().OnDrawControlPoints(dc)
1423
def OnEraseControlPoints(self, dc):
1424
for control in self._controlPoints:
1427
if not isinstance(self, DivisionShape):
1428
for child in self._children:
1429
child.GetEventHandler().OnEraseControlPoints(dc)
1431
def Select(self, select, dc = None):
1432
"""Select or deselect the given shape, drawing or erasing control points
1433
(handles) as necessary.
1435
self._selected = select
1437
self.MakeControlPoints()
1438
# Children of divisions are contained objects,
1440
if not isinstance(self, DivisionShape):
1441
for child in self._children:
1442
child.MakeMandatoryControlPoints()
1444
self.GetEventHandler().OnDrawControlPoints(dc)
1446
self.DeleteControlPoints(dc)
1447
if not isinstance(self, DivisionShape):
1448
for child in self._children:
1449
child.DeleteControlPoints(dc)
1452
"""TRUE if the shape is currently selected."""
1453
return self._selected
1455
def AncestorSelected(self):
1456
"""TRUE if the shape's ancestor is currently selected."""
1459
if not self.GetParent():
1461
return self.GetParent().AncestorSelected()
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.
1469
if len(self._attachmentPoints) == 0:
1473
for point in self._attachmentPoints:
1474
if point._id > maxN:
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)
1483
for point in self._attachmentPoints:
1484
if point._id == attachment:
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.
1491
If attachment isn't found among the attachment points of the shape,
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)
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
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
1514
line and line.IsEnd(self)
1516
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
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)
1532
def GetBoundingBoxMax(self):
1533
"""Get the maximum bounding box for the shape, taking into account
1534
external features such as shadows.
1536
ww, hh = self.GetBoundingBoxMin()
1537
if self._shadowMode != SHADOW_NONE:
1538
ww += self._shadowOffsetX
1539
hh += self._shadowOffsetY
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).
1550
def HasDescendant(self, image):
1551
"""TRUE if image is a descendant of this composite."""
1554
for child in self._children:
1555
if child.HasDescendant(image):
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.
1568
The first point of the line repesenting the edge of the shape.
1571
The second point of the line representing the edge of the shape.
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.
1578
The number of lines at this edge.
1585
This function expects the line to be either vertical or horizontal,
1586
and determines which.
1588
isEnd = line and line.IsEnd(self)
1590
# Are we horizontal or vertical?
1591
isHorizontal = RoughlyEqual(pt1[1], pt2[1])
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]:
1607
elif point[0] > secondPoint[0]:
1612
x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1.0)
1614
x = (secondPoint[0] - firstPoint[0]) / 2.0 # Midpoint
1617
assert RoughlyEqual(pt1[0], pt2[0])
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]:
1632
elif point[1] > secondPoint[1]:
1637
y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1.0)
1639
y = (secondPoint[1] - firstPoint[1]) / 2.0 # Midpoint
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
1650
return self._lines.index(line)
1658
# shoulder1 ->---------<- shoulder2
1660
# <- branching attachment point N-1
1662
def GetBranchingAttachmentInfo(self, attachment):
1663
"""Get information about where branching connections go.
1665
Returns FALSE if there are no lines at this attachment.
1667
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1669
# Number of lines at this attachment
1670
lineCount = self.GetAttachmentLineCount(attachment)
1675
totalBranchLength = self._branchSpacing * (lineCount - 1)
1676
root = self.GetBranchingAttachmentRoot(attachment)
1678
neck = wx.RealPoint()
1679
shoulder1 = wx.RealPoint()
1680
shoulder2 = wx.RealPoint()
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
1687
shoulder1[0] = root[0] - totalBranchLength / 2.0
1688
shoulder2[0] = root[0] + totalBranchLength / 2.0
1690
shoulder1[1] = neck[1]
1691
shoulder2[1] = neck[1]
1692
elif physicalAttachment == 1:
1693
neck[0] = root[0] + self._branchNeckLength
1696
shoulder1[0] = neck[0]
1697
shoulder2[0] = neck[0]
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
1705
shoulder1[0] = root[0] - totalBranchLength / 2.0
1706
shoulder2[0] = root[0] + totalBranchLength / 2.0
1708
shoulder1[1] = neck[1]
1709
shoulder2[1] = neck[1]
1710
elif physicalAttachment == 3:
1711
neck[0] = root[0] - self._branchNeckLength
1714
shoulder1[0] = neck[0]
1715
shoulder2[0] = neck[0]
1717
shoulder1[1] = neck[1] - totalBranchLength / 2.0
1718
shoulder2[1] = neck[1] + totalBranchLength / 2.0
1720
raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
1721
return root, neck, shoulder1, shoulder2
1723
def GetBranchingAttachmentPoint(self, attachment, n):
1724
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1726
root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
1728
stemPt = wx.RealPoint()
1730
if physicalAttachment == 0:
1731
pt[1] = neck[1] - self._branchStemLength
1732
pt[0] = shoulder1[0] + n * self._branchSpacing
1736
elif physicalAttachment == 2:
1737
pt[1] = neck[1] + self._branchStemLength
1738
pt[0] = shoulder1[0] + n * self._branchStemLength
1742
elif physicalAttachment == 1:
1743
pt[0] = neck[0] + self._branchStemLength
1744
pt[1] = shoulder1[1] + n * self._branchSpacing
1748
elif physicalAttachment == 3:
1749
pt[0] = neck[0] - self._branchStemLength
1750
pt[1] = shoulder1[1] + n * self._branchSpacing
1755
raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
1759
def GetAttachmentLineCount(self, attachment):
1760
"""Get the number of lines at this attachment position."""
1762
for lineShape in self._lines:
1763
if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment:
1765
elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment:
1769
def GetBranchingAttachmentRoot(self, attachment):
1770
"""Get the root point at the given attachment."""
1771
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1773
root = wx.RealPoint()
1775
width, height = self.GetBoundingBoxMax()
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()
1791
raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
1795
# Draw or erase the branches (not the actual arcs though)
1796
def OnDrawBranchesAttachment(self, dc, attachment, erase = False):
1797
count = self.GetAttachmentLineCount(attachment)
1801
root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
1804
dc.SetPen(wx.WHITE_PEN)
1805
dc.SetBrush(wx.WHITE_BRUSH)
1807
dc.SetPen(wx.BLACK_PEN)
1808
dc.SetBrush(wx.BLACK_BRUSH)
1811
dc.DrawLine(root[0], root[1], neck[0], neck[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])
1821
if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1:
1823
dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, blobSize, blobSize)
1825
def OnDrawBranches(self, dc, erase = False):
1826
if self._attachmentMode != ATTACHMENT_MODE_BRANCHING:
1828
for i in range(self.GetNumberOfAttachments()):
1829
self.OnDrawBranchesAttachment(dc, i, erase)
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.
1836
oldMode = self._attachmentMode
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
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)
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
1859
# Can't handle -- assume the same
1860
return physicalAttachment
1867
def LogicalToPhysicalAttachment(self, logicalAttachment):
1868
"""Rotate the standard attachment point from logical
1869
to physical (0 is always North).
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
1880
return logicalAttachment
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
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
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
1908
"""Get the x position of the centre of the shape."""
1912
"""Get the y position of the centre of the shape."""
1916
"""Set the x position of the shape."""
1920
"""Set the y position of the shape."""
1923
def GetParent(self):
1924
"""Return the parent of this shape, if it is part of a composite."""
1927
def SetParent(self, p):
1930
def GetChildren(self):
1931
"""Return the list of children for this shape."""
1932
return self._children
1934
def GetDrawHandles(self):
1935
"""Return the list of drawhandles."""
1936
return self._drawHandles
1938
def GetEventHandler(self):
1939
"""Return the event handler for this shape."""
1940
return self._eventHandler
1942
def SetEventHandler(self, handler):
1943
"""Set the event handler for this shape."""
1944
self._eventHandler = handler
1946
def Recompute(self):
1947
"""Recomputes any constraints associated with the shape.
1949
Normally applicable to CompositeShapes only, but harmless for
1950
other classes of Shape.
1954
def IsHighlighted(self):
1955
"""TRUE if the shape is highlighted. Shape highlighting is unimplemented."""
1956
return self._highlighted
1958
def GetSensitivityFilter(self):
1959
"""Return the sensitivity filter, a bitlist of values.
1961
See Shape.SetSensitivityFilter.
1963
return self._sensitivity
1965
def SetFixedSize(self, x, y):
1966
"""Set the shape to be fixed size."""
1967
self._fixedWidth = x
1968
self._fixedHeight = y
1970
def GetFixedSize(self):
1971
"""Return flags indicating whether the shape is of fixed size in
1974
return self._fixedWidth, self._fixedHeight
1976
def GetFixedWidth(self):
1977
"""TRUE if the shape cannot be resized in the horizontal plane."""
1978
return self._fixedWidth
1980
def GetFixedHeight(self):
1981
"""TRUE if the shape cannot be resized in the vertical plane."""
1982
return self._fixedHeight
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
1989
self._spaceAttachments = sp
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
1996
return self._spaceAttachments
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).
2003
self._centreResize = cr
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)
2010
return self._centreResize
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).
2016
self._maintainAspectRatio = ar
2018
def GetMaintainAspectRatio(self):
2019
"""TRUE if shape keeps aspect ratio during resize."""
2020
return self._maintainAspectRatio
2023
"""Return the list of lines connected to this shape."""
2026
def SetDisableLabel(self, flag):
2027
"""Set flag to TRUE to stop the default region being shown."""
2028
self._disableLabel = flag
2030
def GetDisableLabel(self):
2031
"""TRUE if the default region will not be shown, FALSE otherwise."""
2032
return self._disableLabel
2034
def SetAttachmentMode(self, mode):
2035
"""Set the attachment mode.
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.
2041
self._attachmentMode = mode
2043
def GetAttachmentMode(self):
2044
"""Return the attachment mode.
2046
See Shape.SetAttachmentMode.
2048
return self._attachmentMode
2051
"""Set the integer identifier for this shape."""
2055
"""Return the integer identifier for this shape."""
2059
"""TRUE if the shape is in a visible state, FALSE otherwise.
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
2065
return self._visible
2068
"""Return the pen used for drawing the shape's outline."""
2072
"""Return the brush used for filling the shape."""
2075
def GetNumberOfTextRegions(self):
2076
"""Return the number of text regions for this shape."""
2077
return len(self._regions)
2079
def GetRegions(self):
2080
"""Return the list of ShapeRegions."""
2081
return self._regions
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()
2088
dc = wx.ClientDC(self.GetCanvas())
2089
self.GetCanvas().PrepareDC(dc)
2091
dc.SetLogicalFunction(OGLRBLF)
2093
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2094
dc.SetPen(dottedPen)
2095
dc.SetBrush(wx.TRANSPARENT_BRUSH)
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())
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)
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)
2113
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2114
new_height = bound_y * (new_width / bound_x)
2116
if self.GetFixedWidth():
2119
if self.GetFixedHeight():
2120
new_height = bound_y
2122
pt._controlPointDragEndWidth = new_width
2123
pt._controlPointDragEndHeight = new_height
2125
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
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
2143
newY1 = newY2 - newH
2145
newWidth = float(newX2 - newX1)
2146
newHeight = float(newY2 - newY1)
2148
if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2149
newWidth = bound_x * (newHeight / bound_y)
2151
if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2152
newHeight = bound_y * (newWidth / bound_x)
2154
pt._controlPointDragPosX = newX1 + newWidth / 2.0
2155
pt._controlPointDragPosY = newY1 + newHeight / 2.0
2156
if self.GetFixedWidth():
2159
if self.GetFixedHeight():
2162
pt._controlPointDragEndWidth = newWidth
2163
pt._controlPointDragEndHeight = newHeight
2164
self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2166
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2167
self._canvas.CaptureMouse()
2169
dc = wx.ClientDC(self.GetCanvas())
2170
self.GetCanvas().PrepareDC(dc)
2172
dc.SetLogicalFunction(OGLRBLF)
2174
bound_x, bound_y = self.GetBoundingBoxMin()
2175
self.GetEventHandler().OnBeginSize(bound_x, bound_y)
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
2182
pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
2184
if pt.GetY() < self.GetY():
2185
pt._controlPointDragStartY = self.GetY() + bound_y / 2.0
2187
pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
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
2194
# We may require the old width and height
2195
pt._controlPointDragStartWidth = bound_x
2196
pt._controlPointDragStartHeight = bound_y
2198
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2199
dc.SetPen(dottedPen)
2200
dc.SetBrush(wx.TRANSPARENT_BRUSH)
2202
if self.GetCentreResize():
2203
new_width = 2.0 * abs(x - self.GetX())
2204
new_height = 2.0 * abs(y - self.GetY())
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)
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)
2217
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2218
new_height = bound_y * (new_width / bound_x)
2220
if self.GetFixedWidth():
2223
if self.GetFixedHeight():
2224
new_height = bound_y
2226
pt._controlPointDragEndWidth = new_width
2227
pt._controlPointDragEndHeight = new_height
2228
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
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
2246
newY1 = newY2 - newH
2248
newWidth = float(newX2 - newX1)
2249
newHeight = float(newY2 - newY1)
2251
if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2252
newWidth = bound_x * (newHeight / bound_y)
2254
if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2255
newHeight = bound_y * (newWidth / bound_x)
2257
pt._controlPointDragPosX = newX1 + newWidth / 2.0
2258
pt._controlPointDragPosY = newY1 + newHeight / 2.0
2259
if self.GetFixedWidth():
2262
if self.GetFixedHeight():
2265
pt._controlPointDragEndWidth = newWidth
2266
pt._controlPointDragEndHeight = newHeight
2267
self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2269
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2270
dc = wx.ClientDC(self.GetCanvas())
2271
self.GetCanvas().PrepareDC(dc)
2273
if self._canvas.HasCapture():
2274
self._canvas.ReleaseMouse()
2275
dc.SetLogicalFunction(wx.COPY)
2277
self.ResetControlPoints()
2281
self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight)
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.
2289
if self.GetCentreResize():
2290
self.Move(dc, self.GetX(), self.GetY())
2292
self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY)
2294
# Recursively redraw links if we have a composite
2295
if len(self.GetChildren()):
2296
self.DrawLinks(dc, -1, True)
2298
width, height = self.GetBoundingBoxMax()
2299
self.GetEventHandler().OnEndSize(width, height)
2301
if not self._canvas.GetQuickEditMode() and pt._eraseObject:
2302
self._canvas.Redraw(dc)
2306
class RectangleShape(Shape):
2308
The wxRectangleShape has rounded or square corners.
2313
def __init__(self, w = 0.0, h = 0.0):
2314
Shape.__init__(self)
2317
self._cornerRadius = 0.0
2318
self.SetDefaultRegionSize()
2320
def OnDraw(self, dc):
2321
x1 = self._xpos - self._width / 2.0
2322
y1 = self._ypos - self._height / 2.0
2324
if self._shadowMode != SHADOW_NONE:
2325
if self._shadowBrush:
2326
dc.SetBrush(self._shadowBrush)
2327
dc.SetPen(TransparentPen)
2329
if self._cornerRadius:
2330
dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
2332
dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
2335
if self._pen.GetWidth() == 0:
2336
dc.SetPen(TransparentPen)
2338
dc.SetPen(self._pen)
2340
dc.SetBrush(self._brush)
2342
if self._cornerRadius:
2343
dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
2345
dc.DrawRectangle(x1, y1, self._width, self._height)
2347
def GetBoundingBoxMin(self):
2348
return self._width, self._height
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()
2356
def GetCornerRadius(self):
2357
"""Get the radius of the rectangle's rounded corners."""
2358
return self._cornerRadius
2360
def SetCornerRadius(self, rad):
2361
"""Set the radius of the rectangle's rounded corners.
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.
2367
self._cornerRadius = rad
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)
2377
def GetHeight(self):
2380
def SetWidth(self, w):
2383
def SetHeight(self, h):
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.
2394
Shape.__init__(self)
2397
self._originalPoints = None
2399
def Create(self, the_points = None):
2400
"""Takes a list of wx.RealPoints or tuples; each point is an offset
2406
self._originalPoints = []
2409
self._originalPoints = the_points
2411
# Duplicate the list of 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()
2421
def ClearPoints(self):
2423
self._originalPoints = []
2425
# Width and height. Centre of object is centre of box
2426
def GetBoundingBoxMin(self):
2427
return self._boundWidth, self._boundHeight
2429
def GetPoints(self):
2430
"""Return the internal list of polygon vertices."""
2433
def GetOriginalPoints(self):
2434
return self._originalPoints
2436
def GetOriginalWidth(self):
2437
return self._originalWidth
2439
def GetOriginalHeight(self):
2440
return self._originalHeight
2442
def SetOriginalWidth(self, w):
2443
self._originalWidth = w
2445
def SetOriginalHeight(self, h):
2446
self._originalHeight = h
2448
def CalculateBoundingBox(self):
2449
# Calculate bounding box at construction (and presumably resize) time
2455
for point in self._points:
2458
if point[0] > right:
2463
if point[1] > bottom:
2466
self._boundWidth = right - left
2467
self._boundHeight = bottom - top
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
2481
for point in self._points:
2484
if point[0] > right:
2489
if point[1] > bottom:
2492
bwidth = right - left
2493
bheight = bottom - top
2495
newCentreX = left + bwidth / 2.0
2496
newCentreY = top + bheight / 2.0
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
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]
2514
for point in self._points:
2515
xpoints.append(point[0] + self._xpos)
2516
ypoints.append(point[1] + self._ypos)
2518
# We assume it's inside the polygon UNLESS one or more
2519
# lines don't hit the outline.
2523
if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]):
2529
nearest_attachment = 0
2531
# If a hit, check the attachment points within the object
2534
for i in range(self.GetNumberOfAttachments()):
2535
e = self.GetAttachmentPositionEdge(i)
2538
l = math.sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y))
2541
nearest_attachment = i
2543
return nearest_attachment, nearest
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)
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)
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)
2557
self._boundWidth = abs(new_width)
2558
self._boundHeight = abs(new_height)
2559
self.SetDefaultRegionSize()
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.
2566
self._originalPoints = []
2568
for point in self._points:
2569
original_point = wx.RealPoint(point[0], point[1])
2570
self._originalPoints.append(original_point)
2572
self.CalculateBoundingBox()
2573
self._originalWidth = self._boundWidth
2574
self._originalHeight = self._boundHeight
2576
def AddPolygonPoint(self, pos):
2577
"""Add a control point after the given point."""
2579
firstPoint = self._points[pos]
2581
firstPoint = self._points[0]
2584
secondPoint = self._points[pos + 1]
2586
secondPoint = self._points[0]
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)
2592
if pos >= len(self._points) - 1:
2593
self._points.append(point)
2595
self._points.insert(pos + 1, point)
2597
self.UpdateOriginalPoints()
2600
self.DeleteControlPoints()
2601
self.MakeControlPoints()
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()
2609
self.DeleteControlPoints()
2610
self.MakeControlPoints()
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
2621
for point in self._points:
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
2630
for point in self._points:
2631
xpoints.append(point[0] + self._xpos)
2632
ypoints.append(point[1] + self._ypos)
2634
return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2)
2636
def OnDraw(self, dc):
2637
if self._shadowMode != SHADOW_NONE:
2638
if self._shadowBrush:
2639
dc.SetBrush(self._shadowBrush)
2640
dc.SetPen(TransparentPen)
2642
dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY)
2645
if self._pen.GetWidth() == 0:
2646
dc.SetPen(TransparentPen)
2648
dc.SetPen(self._pen)
2650
dc.SetBrush(self._brush)
2651
dc.DrawPolygon(self._points, self._xpos, self._ypos)
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)
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)
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)
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
2678
def GetNumberOfAttachments(self):
2679
maxN = max(len(self._points) - 1, 0)
2680
for point in self._attachmentPoints:
2681
if point._id > maxN:
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)
2691
def AttachmentIsValid(self, attachment):
2692
if not self._points:
2695
if attachment >= 0 and attachment < len(self._points):
2698
for point in self._attachmentPoints:
2699
if point._id == attachment:
2704
# Rotate about the given axis by the given amount in radians
2705
def Rotate(self, x, y, theta):
2706
actualTheta = theta - self._rotation
2708
# Rotate attachment points
2709
sinTheta = math.sin(actualTheta)
2710
cosTheta = math.cos(actualTheta)
2712
for point in self._attachmentPoints:
2716
point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
2717
point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
2719
for i in range(len(self._points)):
2720
x1, y1 = self._points[i]
2722
self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
2724
for i in range(len(self._originalPoints)):
2725
x1, y1 = self._originalPoints[i]
2727
self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
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()
2733
self._rotation = theta
2735
self.CalculatePolygonCentre()
2736
self.CalculateBoundingBox()
2737
self.ResetControlPoints()
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)
2745
dc.SetLogicalFunction(OGLRBLF)
2747
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2748
dc.SetPen(dottedPen)
2749
dc.SetBrush(wx.TRANSPARENT_BRUSH)
2751
# Code for CTRL-drag in C++ version commented out
2753
pt.CalculateNewSize(x, y)
2755
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
2757
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2758
dc = wx.ClientDC(self.GetCanvas())
2759
self.GetCanvas().PrepareDC(dc)
2763
dc.SetLogicalFunction(OGLRBLF)
2765
bound_x, bound_y = self.GetBoundingBoxMin()
2767
dist = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
2769
pt._originalDistance = dist
2770
pt._originalSize[0] = bound_x
2771
pt._originalSize[1] = bound_y
2773
if pt._originalDistance == 0:
2774
pt._originalDistance = 0.0001
2776
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2777
dc.SetPen(dottedPen)
2778
dc.SetBrush(wx.TRANSPARENT_BRUSH)
2780
# Code for CTRL-drag in C++ version commented out
2782
pt.CalculateNewSize(x, y)
2784
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
2786
self._canvas.CaptureMouse()
2788
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2789
dc = wx.ClientDC(self.GetCanvas())
2790
self.GetCanvas().PrepareDC(dc)
2792
if self._canvas.HasCapture():
2793
self._canvas.ReleaseMouse()
2794
dc.SetLogicalFunction(wx.COPY)
2796
# If we're changing shape, must reset the original points
2798
self.CalculateBoundingBox()
2799
self.CalculatePolygonCentre()
2801
self.SetSize(pt.GetNewSize()[0], pt.GetNewSize()[1])
2804
self.ResetControlPoints()
2805
self.Move(dc, self.GetX(), self.GetY())
2806
if not self._canvas.GetQuickEditMode():
2807
self._canvas.Redraw(dc)
2811
class EllipseShape(Shape):
2812
"""The EllipseShape behaves similarly to the RectangleShape but is
2818
def __init__(self, w, h):
2819
Shape.__init__(self)
2822
self.SetDefaultRegionSize()
2824
def GetBoundingBoxMin(self):
2825
return self._width, self._height
2827
def GetPerimeterPoint(self, x1, y1, x2, y2):
2828
bound_x, bound_y = self.GetBoundingBoxMax()
2830
return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1)
2835
def GetHeight(self):
2838
def SetWidth(self, w):
2841
def SetHeight(self, h):
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())
2854
if self._pen.GetWidth() == 0:
2855
dc.SetPen(TransparentPen)
2857
dc.SetPen(self._pen)
2859
dc.SetBrush(self._brush)
2860
dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0, self._ypos - self.GetHeight() / 2.0, self.GetWidth(), self.GetHeight())
2862
def SetSize(self, x, y, recursive = True):
2863
self.SetAttachmentSize(x, y)
2866
self.SetDefaultRegionSize()
2868
def GetNumberOfAttachments(self):
2869
return Shape.GetNumberOfAttachments(self)
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)
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
2883
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
2885
if physicalAttachment == 0:
2886
if self._spaceAttachments:
2887
x = left + (nth + 1) * self._width / (no_arcs + 1.0)
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
2896
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
2897
elif physicalAttachment == 1:
2899
if self._spaceAttachments:
2900
y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
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)
2910
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos)
2911
elif physicalAttachment == 3:
2913
if self._spaceAttachments:
2914
y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
2917
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y)
2919
return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line)
2921
return self._xpos, self._ypos
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)
2931
def GetPerimeterPoint(self, x1, y1, x2, y2):
2932
return FindEndForCircle(self._width / 2.0, self._xpos, self._ypos, x2, y2)
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)
2941
def OnDraw(self, dc):
2946
class ShapeRegion(object):
2947
"""Object region."""
2948
def __init__(self, region = None):
2950
self._regionText = region._regionText
2951
self._regionName = region._regionName
2952
self._textColour = region._textColour
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
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
2971
for line in region._formattedText:
2972
new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText())
2973
self._formattedText.append(new_line)
2975
self._regionText = ""
2976
self._font = NormalFont
2977
self._minHeight = 5.0
2978
self._minWidth = 5.0
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
2994
self._formattedText = []
2996
def ClearText(self):
2997
self._formattedText = []
2999
def SetFont(self, f):
3002
def SetMinSize(self, w, h):
3006
def SetSize(self, w, h):
3010
def SetPosition(self, xp, yp):
3014
def SetProportions(self, xp, yp):
3015
self._regionProportionX = xp
3016
self._regionProportionY = yp
3018
def SetFormatMode(self, mode):
3019
self._formatMode = mode
3021
def SetColour(self, col):
3022
self._textColour = col
3023
self._actualColourObject = col
3025
def GetActualColourObject(self):
3026
self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour())
3027
return self._actualColourObject
3029
def SetPenColour(self, col):
3030
self._penColour = col
3031
self._actualPenObject = None
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
3040
if not self._penColour:
3042
if self._penColour=="Invisible":
3044
self._actualPenObject = wx.Pen(self._penColour, 1, self._penStyle)
3045
return self._actualPenObject
3047
def SetText(self, s):
3048
self._regionText = s
3050
def SetName(self, s):
3051
self._regionName = s
3054
return self._regionText
3059
def GetMinSize(self):
3060
return self._minWidth, self._minHeight
3062
def GetProportion(self):
3063
return self._regionProportionX, self._regionProportionY
3066
return self._width, self._height
3068
def GetPosition(self):
3069
return self._x, self._y
3071
def GetFormatMode(self):
3072
return self._formatMode
3075
return self._regionName
3077
def GetColour(self):
3078
return self._textColour
3080
def GetFormattedText(self):
3081
return self._formattedText
3083
def GetPenColour(self):
3084
return self._penColour
3086
def GetPenStyle(self):
3087
return self._penStyle
3089
def SetPenStyle(self, style):
3090
self._penStyle = style
3091
self._actualPenObject = None
3096
def GetHeight(self):
3101
class ControlPoint(RectangleShape):
3102
def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type):
3103
RectangleShape.__init__(self, size, size)
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
3116
# Don't even attempt to draw any text - waste of time
3117
def OnDrawContents(self, dc):
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)
3125
def OnErase(self, dc):
3126
RectangleShape.OnErase(self, dc)
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)
3132
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3133
self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3135
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3136
self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3138
def GetNumberOfAttachments(self):
3141
def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
3142
return self._xpos, self._ypos
3144
def SetEraseObject(self, er):
3145
self._eraseObject = er
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()
3156
def GetNewSize(self):
3157
return self._newSize
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()))
3164
self._newSize[0] = dist / self._originalDistance * self._originalSize[0]
3165
self._newSize[1] = dist / self._originalDistance * self._originalSize[1]
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)
3171
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3172
self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3174
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3175
self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3177
from _canvas import *
3178
from _lines import *
3179
from _composit import *