1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
4 # Purpose: DrawnShape class
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # License: wxWindows license
12 #----------------------------------------------------------------------------
16 from _basic
import RectangleShape
17 from _oglmisc
import *
20 METAFLAGS_ATTACHMENTS
= 2
31 DRAWOP_SET_TEXT_COLOUR
= 4
32 DRAWOP_SET_BK_COLOUR
= 5
33 DRAWOP_SET_BK_MODE
= 6
34 DRAWOP_SET_CLIPPING_RECT
= 7
35 DRAWOP_DESTROY_CLIPPING_RECT
= 8
38 DRAWOP_DRAW_POLYLINE
= 21
39 DRAWOP_DRAW_POLYGON
= 22
41 DRAWOP_DRAW_ROUNDED_RECT
= 24
42 DRAWOP_DRAW_ELLIPSE
= 25
43 DRAWOP_DRAW_POINT
= 26
46 DRAWOP_DRAW_SPLINE
= 29
47 DRAWOP_DRAW_ELLIPTIC_ARC
= 30
50 def __init__(self
, theOp
):
56 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
, xOffset
, yOffset
, attachmentMode
):
59 def Scale(self
,scaleX
, scaleY
):
62 def Translate(self
, x
, y
):
65 def Rotate(self
, x
, y
, theta
, sinTheta
, cosTheta
):
68 class OpSetGDI(DrawOp
):
69 """Set font, brush, text colour."""
70 def __init__(self
, theOp
, theImage
, theGdiIndex
, theMode
= 0):
71 DrawOp
.__init
__(self
, theOp
)
73 self
._gdiIndex
= theGdiIndex
74 self
._image
= theImage
77 def Do(self
, dc
, xoffset
= 0, yoffset
= 0):
78 if self
._op
== DRAWOP_SET_PEN
:
79 # Check for overriding this operation for outline colour
80 if self
._gdiIndex
in self
._image
._outlineColours
:
81 if self
._image
._outlinePen
:
82 dc
.SetPen(self
._image
._outlinePen
)
85 dc
.SetPen(self
._image
._gdiObjects
[self
._gdiIndex
])
88 elif self
._op
== DRAWOP_SET_BRUSH
:
89 # Check for overriding this operation for outline or fill colour
90 if self
._gdiIndex
in self
._image
._outlineColours
:
91 # Need to construct a brush to match the outline pen's colour
92 if self
._image
._outlinePen
:
93 br
= wx
.Brush(self
._image
._outlinePen
, wx
.SOLID
)
96 elif self
._gdiIndex
in self
._image
._fillColours
:
97 if self
._image
._fillBrush
:
98 dc
.SetBrush(self
._image
._fillBrush
)
100 brush
= self
._image
._gdiObjects
[self
._gdiIndex
]
103 elif self
._op
== DRAWOP_SET_FONT
:
105 dc
.SetFont(self
._image
._gdiObjects
[self
._gdiIndex
])
108 elif self
._op
== DRAWOP_SET_TEXT_COLOUR
:
109 dc
.SetTextForeground(wx
.Colour(self
._r
, self
._g
, self
._b
))
110 elif self
._op
== DRAWOP_SET_BK_COLOUR
:
111 dc
.SetTextBackground(wx
.Colour(self
._r
, self
._g
, self
._b
))
112 elif self
._op
== DRAWOP_SET_BK_MODE
:
113 dc
.SetBackgroundMode(self
._mode
)
116 class OpSetClipping(DrawOp
):
117 """Set/destroy clipping."""
118 def __init__(self
, theOp
, theX1
, theY1
, theX2
, theY2
):
119 DrawOp
.__init
__(self
, theOp
)
126 def Do(self
, dc
, xoffset
, yoffset
):
127 if self
._op
== DRAWOP_SET_CLIPPING_RECT
:
128 dc
.SetClippingRegion(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2 + xoffset
, self
._y
2 + yoffset
)
129 elif self
._op
== DRAWOP_DESTROY_CLIPPING_RECT
:
130 dc
.DestroyClippingRegion()
132 def Scale(self
, scaleX
, scaleY
):
138 def Translate(self
, x
, y
):
143 class OpDraw(DrawOp
):
144 """Draw line, rectangle, rounded rectangle, ellipse, point, arc, text."""
145 def __init__(self
, theOp
, theX1
, theY1
, theX2
, theY2
, theRadius
= 0.0, s
= ""):
146 DrawOp
.__init
__(self
, theOp
)
154 self
._radius
= theRadius
157 def Do(self
, dc
, xoffset
, yoffset
):
158 if self
._op
== DRAWOP_DRAW_LINE
:
159 dc
.DrawLine(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2 + xoffset
, self
._y
2 + yoffset
)
160 elif self
._op
== DRAWOP_DRAW_RECT
:
161 dc
.DrawRectangle(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2, self
._y
2)
162 elif self
._op
== DRAWOP_DRAW_ROUNDED_RECT
:
163 dc
.DrawRoundedRectangle(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2, self
._y
2, self
._radius
)
164 elif self
._op
== DRAWOP_DRAW_ELLIPSE
:
165 dc
.DrawEllipse(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2, self
._y
2)
166 elif self
._op
== DRAWOP_DRAW_ARC
:
167 dc
.DrawArc(self
._x
2 + xoffset
, self
._y
2 + yoffset
, self
._x
3 + xoffset
, self
._y
3 + yoffset
, self
._x
1 + xoffset
, self
._y
1 + yoffset
)
168 elif self
._op
== DRAWOP_DRAW_ELLIPTIC_ARC
:
169 dc
.DrawEllipticArc(self
._x
1 + xoffset
, self
._y
1 + yoffset
, self
._x
2, self
._y
2, self
._x
3 * 360 / (2 * math
.pi
), self
._y
3 * 360 / (2 * math
.pi
))
170 elif self
._op
== DRAWOP_DRAW_POINT
:
171 dc
.DrawPoint(self
._x
1 + xoffset
, self
._y
1 + yoffset
)
172 elif self
._op
== DRAWOP_DRAW_TEXT
:
173 dc
.DrawText(self
._textString
, self
._x
1 + xoffset
, self
._y
1 + yoffset
)
174 def Scale(self
, scaleX
, scaleY
):
180 if self
._op
!= DRAWOP_DRAW_ELLIPTIC_ARC
:
184 self
._radius
*= scaleX
186 def Translate(self
, x
, y
):
190 if self
._op
== DRAWOP_DRAW_LINE
:
193 elif self
._op
== DRAWOP_DRAW_ARC
:
199 def Rotate(self
, x
, y
, theta
, sinTheta
, cosTheta
):
200 newX1
= self
._x
1 * cosTheta
+ self
._y
1 * sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
201 newY1
= self
._x
1 * sinTheta
+ self
._y
1 * cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
203 if self
._op
== DRAWOP_DRAW_LINE
:
204 newX2
= self
._x
2 * cosTheta
- self
._y
2 * sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
205 newY2
= self
._x
2 * sinTheta
+ self
._y
2 * cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
;
212 elif self
._op
in [DRAWOP_DRAW_RECT
, DRAWOP_DRAW_ROUNDED_RECT
, DRAWOP_DRAW_ELLIPTIC_ARC
]:
213 # Assume only 0, 90, 180, 270 degree rotations.
214 # oldX1, oldY1 represents the top left corner. Find the
215 # bottom right, and rotate that. Then the width/height is
216 # the difference between x/y values.
217 oldBottomRightX
= self
._x
1 + self
._x
2
218 oldBottomRightY
= self
._y
1 + self
._y
2
219 newBottomRightX
= oldBottomRightX
* cosTheta
- oldBottomRightY
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
220 newBottomRightY
= oldBottomRightX
* sinTheta
+ oldBottomRightY
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
222 # Now find the new top-left, bottom-right coordinates.
223 minX
= min(newX1
, newBottomRightX
)
224 minY
= min(newY1
, newBottomRightY
)
225 maxX
= max(newX1
, newBottomRightX
)
226 maxY
= max(newY1
, newBottomRightY
)
230 self
._x
2 = maxX
- minX
# width
231 self
._y
2 = maxY
- minY
# height
233 if self
._op
== DRAWOP_DRAW_ELLIPTIC_ARC
:
234 # Add rotation to angles
237 elif self
._op
== DRAWOP_DRAW_ARC
:
238 newX2
= self
._x
2 * cosTheta
- self
._y
2 * sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
239 newY2
= self
._x
2 * sinTheta
+ self
._y
2 * cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
240 newX3
= self
._x
3 * cosTheta
- self
._y
3 * sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
241 newY3
= self
._x
3 * sinTheta
+ self
._y
3 * cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
251 class OpPolyDraw(DrawOp
):
252 """Draw polygon, polyline, spline."""
253 def __init__(self
, theOp
, thePoints
):
254 DrawOp
.__init
__(self
, theOp
)
256 self
._noPoints
= len(thePoints
)
257 self
._points
= thePoints
259 def Do(self
, dc
, xoffset
, yoffset
):
260 if self
._op
== DRAWOP_DRAW_POLYLINE
:
261 dc
.DrawLines(self
._points
, xoffset
, yoffset
)
262 elif self
._op
== DRAWOP_DRAW_POLYGON
:
263 dc
.DrawPolygon(self
._points
, xoffset
, yoffset
)
264 elif self
._op
== DRAWOP_DRAW_SPLINE
:
265 dc
.DrawSpline(self
._points
) # no offsets in DrawSpline
267 def Scale(self
, scaleX
, scaleY
):
268 for i
in range(self
._noPoints
):
269 self
._points
[i
] = wx
.Point(self
._points
[i
][0] * scaleX
, self
._points
[i
][1] * scaleY
)
271 def Translate(self
, x
, y
):
272 for i
in range(self
._noPoints
):
273 self
._points
[i
][0] += x
274 self
._points
[i
][1] += y
276 def Rotate(self
, x
, y
, theta
, sinTheta
, cosTheta
):
277 for i
in range(self
._noPoints
):
278 x1
= self
._points
[i
][0]
279 y1
= self
._points
[i
][1]
281 self
._points
[i
] = x1
* cosTheta
- y1
* sinTheta
+ x
* (1 - cosTheta
) + y
* sinTheta
, x1
* sinTheta
+ y1
* cosTheta
+ y
* (1 - cosTheta
) + x
* sinTheta
283 def OnDrawOutline(self
, dc
, x
, y
, w
, h
, oldW
, oldH
):
284 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
286 # Multiply all points by proportion of new size to old size
287 x_proportion
= abs(w
/ oldW
)
288 y_proportion
= abs(h
/ oldH
)
290 dc
.DrawPolygon([(x_proportion
* x
, y_proportion
* y
) for x
, y
in self
._points
], x
, y
)
292 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
, xOffset
, yOffset
, attachmentMode
):
293 # First check for situation where the line is vertical,
294 # and we would want to connect to a point on that vertical --
295 # oglFindEndForPolyline can't cope with this (the arrow
296 # gets drawn to the wrong place).
297 if attachmentMode
== ATTACHMENT_MODE_NONE
and x1
== x2
:
298 # Look for the point we'd be connecting to. This is
300 for point
in self
._points
:
302 if y2
> y1
and point
[1] > 0:
303 return point
[0]+xOffset
, point
[1]+yOffset
304 elif y2
< y1
and point
[1] < 0:
305 return point
[0]+xOffset
, point
[1]+yOffset
307 return FindEndForPolyline([ p
[0] + xOffset
for p
in self
._points
],
308 [ p
[1] + yOffset
for p
in self
._points
],
312 class PseudoMetaFile(object):
314 A simple metafile-like class which can load data from a Windows
315 metafile on all platforms.
318 self
._currentRotation
= 0
319 self
._rotateable
= True
322 self
._outlinePen
= None
323 self
._fillBrush
= None
327 self
._gdiObjects
= []
329 self
._outlineColours
= []
330 self
._fillColours
= []
334 self
._gdiObjects
= []
335 self
._outlineColours
= []
336 self
._fillColours
= []
337 self
._outlineColours
= -1
340 return self
._ops
!= []
345 def SetOutlineOp(self
, op
):
348 def GetOutlineOp(self
):
349 return self
._outlineOp
351 def SetOutlinePen(self
, pen
):
352 self
._outlinePen
= pen
354 def GetOutlinePen(self
, pen
):
355 return self
._outlinePen
357 def SetFillBrush(self
, brush
):
358 self
._fillBrush
= brush
360 def GetFillBrush(self
):
361 return self
._fillBrush
363 def SetSize(self
, w
, h
):
367 def SetRotateable(self
, rot
):
368 self
._rotateable
= rot
370 def GetRotateable(self
):
371 return self
._rotateable
373 def GetFillColours(self
):
374 return self
._fillColours
376 def GetOutlineColours(self
):
377 return self
._outlineColours
379 def Draw(self
, dc
, xoffset
, yoffset
):
381 op
.Do(dc
, xoffset
, yoffset
)
383 def Scale(self
, sx
, sy
):
390 def Translate(self
, x
, y
):
394 def Rotate(self
, x
, y
, theta
):
395 theta1
= theta
- self
._currentRotation
399 cosTheta
= math
.cos(theta1
)
400 sinTheta
= math
.sin(theta1
)
403 op
.Rotate(x
, y
, theta
, sinTheta
, cosTheta
)
405 self
._currentRotation
= theta
407 def LoadFromMetaFile(self
, filename
, rwidth
, rheight
):
408 if not os
.path
.exist(filename
):
411 print "LoadFromMetaFile not implemented yet."
415 def ScaleTo(self
, w
, h
):
416 scaleX
= w
/ self
._width
417 scaleY
= h
/ self
._height
419 self
.Scale(scaleX
, scaleY
)
422 maxX
, maxY
, minX
, minY
= -99999.9, -99999.9, 99999.9, 99999.9
425 if op
.GetOp() in [DRAWOP_DRAW_LINE
, DRAWOP_DRAW_RECT
, DRAWOP_DRAW_ROUNDED_RECT
, DRAWOP_DRAW_ELLIPSE
, DRAWOP_DRAW_POINT
, DRAWOP_DRAW_TEXT
]:
434 if op
.GetOp() == DRAWOP_DRAW_LINE
:
443 elif op
.GetOp() in [ DRAWOP_DRAW_RECT
, DRAWOP_DRAW_ROUNDED_RECT
, DRAWOP_DRAW_ELLIPSE
]:
444 if op
._x
1 + op
._x
2 < minX
:
445 minX
= op
._x
1 + op
._x
2
446 if op
._x
1 + op
._x
2 > maxX
:
447 maxX
= op
._x
1 + op
._x
2
448 if op
._y
1 + op
._y
2 < minY
:
449 minY
= op
._y
1 + op
._y
2
450 if op
._y
1 + op
._y
2 > maxX
:
451 maxY
= op
._y
1 + op
._y
2
452 elif op
.GetOp() == DRAWOP_DRAW_ARC
:
453 # TODO: don't yet know how to calculate the bounding box
454 # for an arc. So pretend it's a line; to get a correct
455 # bounding box, draw a blank rectangle first, of the
473 elif op
.GetOp() in [DRAWOP_DRAW_POLYLINE
, DRAWOP_DRAW_POLYGON
, DRAWOP_DRAW_SPLINE
]:
474 for point
in op
._points
:
484 return [minX
, minY
, maxX
, maxY
]
486 # Calculate size from current operations
487 def CalculateSize(self
, shape
):
488 boundMinX
, boundMinY
, boundMaxX
, boundMaxY
= self
.GetBounds()
490 # By Pierre Hjälm: This is NOT in the old version, which
491 # gets this totally wrong. Since the drawing is centered, we
492 # cannot get the width by measuring from left to right, we
493 # must instead make enough room to handle the largest
495 #self.SetSize(boundMaxX - boundMinX, boundMaxY - boundMinY)
497 w
= max(abs(boundMinX
), abs(boundMaxX
)) * 2
498 h
= max(abs(boundMinY
), abs(boundMaxY
)) * 2
503 shape
.SetWidth(self
._width
)
504 shape
.SetHeight(self
._height
)
506 # Set of functions for drawing into a pseudo metafile
507 def DrawLine(self
, pt1
, pt2
):
508 op
= OpDraw(DRAWOP_DRAW_LINE
, pt1
[0], pt1
[1], pt2
[0], pt2
[1])
511 def DrawRectangle(self
, rect
):
512 op
= OpDraw(DRAWOP_DRAW_RECT
, rect
[0], rect
[1], rect
[2], rect
[3])
515 def DrawRoundedRectangle(self
, rect
, radius
):
516 op
= OpDraw(DRAWOP_DRAW_ROUNDED_RECT
, rect
[0], rect
[1], rect
[2], rect
[3])
520 def DrawEllipse(self
, rect
):
521 op
= OpDraw(DRAWOP_DRAW_ELLIPSE
, rect
[0], rect
[1], rect
[2], rect
[3])
524 def DrawArc(self
, centrePt
, startPt
, endPt
):
525 op
= OpDraw(DRAWOP_DRAW_ARC
, centrePt
[0], centrePt
[1], startPt
[0], startPt
[1])
526 op
._x
3, op
._y
3 = endPt
530 def DrawEllipticArc(self
, rect
, startAngle
, endAngle
):
531 startAngleRadians
= startAngle
* math
.pi
* 2 / 360
532 endAngleRadians
= endAngle
* math
.pi
* 2 / 360
534 op
= OpDraw(DRAWOP_DRAW_ELLIPTIC_ARC
, rect
[0], rect
[1], rect
[2], rect
[3])
535 op
._x
3 = startAngleRadians
536 op
._y
3 = endAngleRadians
540 def DrawPoint(self
, pt
):
541 op
= OpDraw(DRAWOP_DRAW_POINT
, pt
[0], pt
[1], 0, 0)
544 def DrawText(self
, text
, pt
):
545 op
= OpDraw(DRAWOP_DRAW_TEXT
, pt
[0], pt
[1], 0, 0)
546 op
._textString
= text
549 def DrawLines(self
, pts
):
550 op
= OpPolyDraw(DRAWOP_DRAW_POLYLINE
, pts
)
554 # oglMETAFLAGS_OUTLINE: will be used for drawing the outline and
555 # also drawing lines/arrows at the circumference.
556 # oglMETAFLAGS_ATTACHMENTS: will be used for initialising attachment
557 # points at the vertices (perhaps a rare case...)
558 def DrawPolygon(self
, pts
, flags
= 0):
559 op
= OpPolyDraw(DRAWOP_DRAW_POLYGON
, pts
)
562 if flags
& METAFLAGS_OUTLINE
:
563 self
._outlineOp
= len(self
._ops
) - 1
565 def DrawSpline(self
, pts
):
566 op
= OpPolyDraw(DRAWOP_DRAW_SPLINE
, pts
)
569 def SetClippingRect(self
, rect
):
570 OpSetClipping(DRAWOP_SET_CLIPPING_RECT
, rect
[0], rect
[1], rect
[2], rect
[3])
572 def DestroyClippingRect(self
):
573 op
= OpSetClipping(DRAWOP_DESTROY_CLIPPING_RECT
, 0, 0, 0, 0)
576 def SetPen(self
, pen
, isOutline
= False):
577 self
._gdiObjects
.append(pen
)
578 op
= OpSetGDI(DRAWOP_SET_PEN
, self
, len(self
._gdiObjects
) - 1)
582 self
._outlineColours
.append(len(self
._gdiObjects
) - 1)
584 def SetBrush(self
, brush
, isFill
= False):
585 self
._gdiObjects
.append(brush
)
586 op
= OpSetGDI(DRAWOP_SET_BRUSH
, self
, len(self
._gdiObjects
) - 1)
590 self
._fillColours
.append(len(self
._gdiObjects
) - 1)
592 def SetFont(self
, font
):
593 self
._gdiObjects
.append(font
)
594 op
= OpSetGDI(DRAWOP_SET_FONT
, self
, len(self
._gdiObjects
) - 1)
597 def SetTextColour(self
, colour
):
598 op
= OpSetGDI(DRAWOP_SET_TEXT_COLOUR
, self
, 0)
599 op
._r
, op
._g
, op
._b
= colour
.Red(), colour
.Green(), colour
.Blue()
603 def SetBackgroundColour(self
, colour
):
604 op
= OpSetGDI(DRAWOP_SET_BK_COLOUR
, self
, 0)
605 op
._r
, op
._g
, op
._b
= colour
.Red(), colour
.Green(), colour
.Blue()
609 def SetBackgroundMode(self
, mode
):
610 op
= OpSetGDI(DRAWOP_SET_BK_MODE
, self
, 0)
613 class DrawnShape(RectangleShape
):
615 Draws a pseudo-metafile shape, which can be loaded from a simple
618 wxDrawnShape allows you to specify a different shape for each of four
619 orientations (North, West, South and East). It also provides a set of
620 drawing functions for programmatic drawing of a shape, so that during
621 construction of the shape you can draw into it as if it were a device
628 RectangleShape
.__init
__(self
, 100, 50)
629 self
._saveToFile
= True
630 self
._currentAngle
= DRAWN_ANGLE_0
632 self
._metafiles
=PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile()
634 def OnDraw(self
, dc
):
635 # Pass pen and brush in case we have force outline
637 if self
._shadowMode
!= SHADOW_NONE
:
638 if self
._shadowBrush
:
639 self
._metafiles
[self
._currentAngle
]._fillBrush
= self
._shadowBrush
640 self
._metafiles
[self
._currentAngle
]._outlinePen
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)
641 self
._metafiles
[self
._currentAngle
].Draw(dc
, self
._xpos
+ self
._shadowOffsetX
, self
._ypos
+ self
._shadowOffsetY
)
643 self
._metafiles
[self
._currentAngle
]._outlinePen
= self
._pen
644 self
._metafiles
[self
._currentAngle
]._fillBrush
= self
._brush
645 self
._metafiles
[self
._currentAngle
].Draw(dc
, self
._xpos
, self
._ypos
)
647 def SetSize(self
, w
, h
, recursive
= True):
648 self
.SetAttachmentSize(w
, h
)
650 if self
.GetWidth() == 0.0:
653 scaleX
= w
/ self
.GetWidth()
655 if self
.GetHeight() == 0.0:
658 scaleY
= h
/ self
.GetHeight()
661 if self
._metafiles
[i
].IsValid():
662 self
._metafiles
[i
].Scale(scaleX
, scaleY
)
666 self
.SetDefaultRegionSize()
668 def Scale(self
, sx
, sy
):
669 """Scale the shape by the given amount."""
671 if self
._metafiles
[i
].IsValid():
672 self
._metafiles
[i
].Scale(sx
, sy
)
673 self
._metafiles
[i
].CalculateSize(self
)
675 def Translate(self
, x
, y
):
676 """Translate the shape by the given amount."""
678 if self
._metafiles
[i
].IsValid():
679 self
._metafiles
[i
].Translate(x
, y
)
680 self
._metafiles
[i
].CalculateSize(self
)
682 # theta is absolute rotation from the zero position
683 def Rotate(self
, x
, y
, theta
):
684 """Rotate about the given axis by the given amount in radians."""
685 self
._currentAngle
= self
.DetermineMetaFile(theta
)
687 if self
._currentAngle
== 0:
689 if not self
._metafiles
[0].GetRotateable():
692 self
._metafiles
[0].Rotate(x
, y
, theta
)
694 actualTheta
= theta
- self
._rotation
696 # Rotate attachment points
697 sinTheta
= math
.sin(actualTheta
)
698 cosTheta
= math
.cos(actualTheta
)
700 for point
in self
._attachmentPoints
:
704 point
._x
= x1
* cosTheta
- y1
* sinTheta
+ x
* (1.0 - cosTheta
) + y
* sinTheta
705 point
._y
= x1
* sinTheta
+ y1
* cosTheta
+ y
* (1.0 - cosTheta
) + x
* sinTheta
707 self
._rotation
= theta
709 self
._metafiles
[self
._currentAngle
].CalculateSize(self
)
711 # Which metafile do we use now? Based on current rotation and validity
713 def DetermineMetaFile(self
, rotation
):
715 angles
= [0.0, math
.pi
/ 2, math
.pi
, 3 * math
.pi
/ 2]
720 if RoughlyEqual(rotation
, angles
[i
], tolerance
):
724 if whichMetaFile
> 0 and not self
._metafiles
[whichMetaFile
].IsValid():
729 def OnDrawOutline(self
, dc
, x
, y
, w
, h
):
730 if self
._metafiles
[self
._currentAngle
].GetOutlineOp() != -1:
731 op
= self
._metafiles
[self
._currentAngle
].GetOps()[self
._metafiles
[self
._currentAngle
].GetOutlineOp()]
732 if op
.OnDrawOutline(dc
, x
, y
, w
, h
, self
._width
, self
._height
):
735 # Default... just use a rectangle
736 RectangleShape
.OnDrawOutline(self
, dc
, x
, y
, w
, h
)
738 # Get the perimeter point using the special outline op, if there is one,
739 # otherwise use default wxRectangleShape scheme
740 def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
):
741 if self
._metafiles
[self
._currentAngle
].GetOutlineOp() != -1:
742 op
= self
._metafiles
[self
._currentAngle
].GetOps()[self
._metafiles
[self
._currentAngle
].GetOutlineOp()]
743 p
= op
.GetPerimeterPoint(x1
, y1
, x2
, y2
, self
.GetX(), self
.GetY(), self
.GetAttachmentMode())
747 return RectangleShape
.GetPerimeterPoint(self
, x1
, y1
, x2
, y2
)
749 def LoadFromMetaFile(self
, filename
):
750 """Load a (very simple) Windows metafile, created for example by
751 Top Draw, the Windows shareware graphics package."""
752 return self
._metafiles
[0].LoadFromMetaFile(filename
)
754 # Set of functions for drawing into a pseudo metafile.
755 # They use integers, but doubles are used internally for accuracy
757 def DrawLine(self
, pt1
, pt2
):
758 self
._metafiles
[self
._currentAngle
].DrawLine(pt1
, pt2
)
760 def DrawRectangle(self
, rect
):
761 self
._metafiles
[self
._currentAngle
].DrawRectangle(rect
)
763 def DrawRoundedRectangle(self
, rect
, radius
):
764 """Draw a rounded rectangle.
766 radius is the corner radius. If radius is negative, it expresses
767 the radius as a proportion of the smallest dimension of the rectangle.
769 self
._metafiles
[self
._currentAngle
].DrawRoundedRectangle(rect
, radius
)
771 def DrawEllipse(self
, rect
):
772 self
._metafiles
[self
._currentAngle
].DrawEllipse(rect
)
774 def DrawArc(self
, centrePt
, startPt
, endPt
):
776 self
._metafiles
[self
._currentAngle
].DrawArc(centrePt
, startPt
, endPt
)
778 def DrawEllipticArc(self
, rect
, startAngle
, endAngle
):
779 """Draw an elliptic arc."""
780 self
._metafiles
[self
._currentAngle
].DrawEllipticArc(rect
, startAngle
, endAngle
)
782 def DrawPoint(self
, pt
):
783 self
._metafiles
[self
._currentAngle
].DrawPoint(pt
)
785 def DrawText(self
, text
, pt
):
786 self
._metafiles
[self
._currentAngle
].DrawText(text
, pt
)
788 def DrawLines(self
, pts
):
789 self
._metafiles
[self
._currentAngle
].DrawLines(pts
)
791 def DrawPolygon(self
, pts
, flags
= 0):
794 flags can be one or more of:
795 METAFLAGS_OUTLINE (use this polygon for the drag outline) and
796 METAFLAGS_ATTACHMENTS (use the vertices of this polygon for attachments).
798 if flags
and METAFLAGS_ATTACHMENTS
:
799 self
.ClearAttachments()
800 for i
in range(len(pts
)):
801 self
._attachmentPoints
.append(AttachmentPoint(i
,pts
[i
][0],pts
[i
][1]))
802 self
._metafiles
[self
._currentAngle
].DrawPolygon(pts
, flags
)
804 def DrawSpline(self
, pts
):
805 self
._metafiles
[self
._currentAngle
].DrawSpline(pts
)
807 def SetClippingRect(self
, rect
):
808 """Set the clipping rectangle."""
809 self
._metafiles
[self
._currentAngle
].SetClippingRect(rect
)
811 def DestroyClippingRect(self
):
812 """Destroy the clipping rectangle."""
813 self
._metafiles
[self
._currentAngle
].DestroyClippingRect()
815 def SetDrawnPen(self
, pen
, isOutline
= False):
816 """Set the pen for this metafile.
818 If isOutline is True, this pen is taken to indicate the outline
819 (and if the outline pen is changed for the whole shape, the pen
820 will be replaced with the outline pen).
822 self
._metafiles
[self
._currentAngle
].SetPen(pen
, isOutline
)
824 def SetDrawnBrush(self
, brush
, isFill
= False):
825 """Set the brush for this metafile.
827 If isFill is True, the brush is used as the fill brush.
829 self
._metafiles
[self
._currentAngle
].SetBrush(brush
, isFill
)
831 def SetDrawnFont(self
, font
):
832 self
._metafiles
[self
._currentAngle
].SetFont(font
)
834 def SetDrawnTextColour(self
, colour
):
835 """Set the current text colour for the current metafile."""
836 self
._metafiles
[self
._currentAngle
].SetTextColour(colour
)
838 def SetDrawnBackgroundColour(self
, colour
):
839 """Set the current background colour for the current metafile."""
840 self
._metafiles
[self
._currentAngle
].SetBackgroundColour(colour
)
842 def SetDrawnBackgroundMode(self
, mode
):
843 """Set the current background mode for the current metafile."""
844 self
._metafiles
[self
._currentAngle
].SetBackgroundMode(mode
)
846 def CalculateSize(self
):
847 """Calculate the wxDrawnShape size from the current metafile.
849 Call this after you have drawn into the shape.
851 self
._metafiles
[self
._currentAngle
].CalculateSize(self
)
853 def DrawAtAngle(self
, angle
):
854 """Set the metafile for the given orientation, which can be one of:
861 self
._currentAngle
= angle
864 """Return the current orientation, which can be one of:
871 return self
._currentAngle
873 def GetRotation(self
):
874 """Return the current rotation of the shape in radians."""
875 return self
._rotation
877 def SetSaveToFile(self
, save
):
878 """If save is True, the image will be saved along with the shape's
879 other attributes. The reason why this might not be desirable is that
880 if there are many shapes with the same image, it would be more
881 efficient for the application to save one copy, and not duplicate
882 the information for every shape. The default is True.
884 self
._saveToFile
= save
886 def GetMetaFile(self
, which
= 0):
887 """Return a reference to the internal 'pseudo-metafile'."""
888 return self
._metafiles
[which
]