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