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