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