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