]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_drawn.py
Adding DrawnShape (patch from Pierre Hjälm)
[wxWidgets.git] / wxPython / wx / lib / ogl / _drawn.py
1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
3 # Name: drawn.py
4 # Purpose: DrawnShape class
5 #
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
7 #
8 # Created: 2004-08-25
9 # RCS-ID: $Id$
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # License: wxWindows license
12 #----------------------------------------------------------------------------
13
14 import os.path
15
16 from _basic import RectangleShape
17 from _oglmisc import *
18
19 METAFLAGS_OUTLINE = 1
20 METAFLAGS_ATTACHMENTS = 2
21
22 DRAWN_ANGLE_0 = 0
23 DRAWN_ANGLE_90 = 1
24 DRAWN_ANGLE_180 = 2
25 DRAWN_ANGLE_270 = 3
26
27 # Drawing operations
28 DRAWOP_SET_PEN = 1
29 DRAWOP_SET_BRUSH = 2
30 DRAWOP_SET_FONT = 3
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
36
37 DRAWOP_DRAW_LINE = 20
38 DRAWOP_DRAW_POLYLINE = 21
39 DRAWOP_DRAW_POLYGON = 22
40 DRAWOP_DRAW_RECT = 23
41 DRAWOP_DRAW_ROUNDED_RECT = 24
42 DRAWOP_DRAW_ELLIPSE = 25
43 DRAWOP_DRAW_POINT = 26
44 DRAWOP_DRAW_ARC = 27
45 DRAWOP_DRAW_TEXT = 28
46 DRAWOP_DRAW_SPLINE = 29
47 DRAWOP_DRAW_ELLIPTIC_ARC = 30
48
49 class DrawOp(object):
50 def __init__(self, theOp):
51 self._op = theOp
52
53 def GetOp(self):
54 return self._op
55
56 def GetPerimeterPoint(self, x1, y1, x2, y2, xOffset, yOffset, attachmentMode):
57 return False
58
59 def Scale(self,scaleX, scaleY):
60 pass
61
62 def Translate(self, x, y):
63 pass
64
65 class OpSetGDI(DrawOp):
66 """Set font, brush, text colour."""
67 def __init__(self, theOp, theImage, theGdiIndex, theMode = 0):
68 DrawOp.__init__(self, theOp)
69
70 self._gdiIndex = theGdiIndex
71 self._image = theImage
72 self._mode = theMode
73
74 def Do(self, dc, xoffset = 0, yoffset = 0):
75 if self._op == DRAWOP_SET_PEN:
76 # Check for overriding this operation for outline colour
77 if self._gdiIndex in self._image._outlineColours:
78 if self._image._outlinePen:
79 dc.SetPen(self._image._outlinePen)
80 else:
81 try:
82 dc.SetPen(self._image._gdiObjects[self._gdiIndex])
83 except IndexError:
84 pass
85 elif self._op == DRAWOP_SET_BRUSH:
86 # Check for overriding this operation for outline or fill colour
87 if self._gdiIndex in self._image._outlineColours:
88 # Need to construct a brush to match the outline pen's colour
89 if self._image._outlinePen:
90 br = wx.TheBrushList.FindOrCreateBrush(self._image._outlinePen, wx.SOLID)
91 if br:
92 dc.SetBrush(br)
93 elif self._gdiIndex in self._image._fillColours:
94 if self._image._fillBrush:
95 dc.SetBrush(self._image._fillBrush)
96 else:
97 brush = self._image._gdiObjects[self._gdiIndex]
98 if brush:
99 dc.SetBrush(brush)
100 elif self._op == DRAWOP_SET_FONT:
101 try:
102 dc.SetFont(self._image._gdiObjects[self._gdiIndex])
103 except IndexError:
104 pass
105 elif self._op == DRAWOP_SET_TEXT_COLOUR:
106 dc.SetTextForeground(wx.Colour(self._r, self._g, self._b))
107 elif self._op == DRAWOP_SET_BK_COLOUR:
108 dc.SetTextBackground(wx.Colour(self._r, self._g, self._b))
109 elif self._op == DRAWOP_SET_BK_MODE:
110 dc.SetBackgroundMode(self._mode)
111
112
113 class OpSetClipping(DrawOp):
114 """Set/destroy clipping."""
115 def __init__(self, theOp, theX1, theY1, theX2, theY2):
116 DrawOp.__init__(self, theOp)
117
118 self._x1 = theX1
119 self._y1 = theY1
120 self._x2 = theX2
121 self._y2 = theY2
122
123 def Do(self, dc, xoffset, yoffset):
124 if self._op == DRAWOP_SET_CLIPPING_RECT:
125 dc.SetClippingRegion(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
126 elif self._op == DRAWOP_DESTROY_CLIPPING_RECT:
127 dc.DestroyClippingRegion()
128
129 def Scale(self, scaleX, scaleY):
130 self._x1 *= scaleX
131 self._y1 *= scaleY
132 self._x2 *= scaleX
133 self._y2 *= scaleY
134
135 def Translate(self, x, y):
136 self._x1 += x
137 self._y1 += y
138
139
140 class OpDraw(DrawOp):
141 """Draw line, rectangle, rounded rectangle, ellipse, point, arc, text."""
142 def __init__(self, theOp, theX1, theY1, theX2, theY2, theRadius = 0.0, s = ""):
143 DrawOp.__init__(self, theOp)
144
145 self._x1 = theX1
146 self._y1 = theY1
147 self._x2 = theX2
148 self._y2 = theY2
149 self._x3 = 0.0
150 self._y3 = 0.0
151 self._radius = theRadius
152 self._textString = s
153
154 def Do(self, dc, xoffset, yoffset):
155 if self._op == DRAWOP_DRAW_LINE:
156 dc.DrawLine(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
157 elif self._op == DRAWOP_DRAW_RECT:
158 dc.DrawRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
159 elif self._op == DRAWOP_DRAW_ROUNDED_RECT:
160 dc.DrawRoundedRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._radius)
161 elif self._op == DRAWOP_DRAW_ELLIPSE:
162 dc.DrawEllipse(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
163 elif self._op == DRAWOP_DRAW_ARC:
164 dc.DrawArc(self._x2 + xoffset, self._y2 + yoffset, self._x3 + xoffset, self._y3 + yoffset, self._x1 + xoffset, self._y1 + yoffset)
165 elif self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
166 dc.DrawEllipticArc(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._x3 * 360 / (2 * math.pi), self.y3 * 360 / (2 * math.pi))
167 elif self._op == DRAWOP_DRAW_POINT:
168 dc.DrawPoint(self._x1 + xoffset, self._y1 + yoffset)
169 elif self._op == DRAWOP_DRAW_TEXT:
170 dc.DrawText(self._textString, self._x1 + xoffset, self._y1 + yoffset)
171 def Scale(self, scaleX, scaleY):
172 self._x1 *= scaleX
173 self._y1 *= scaleY
174 self._x2 *= scaleX
175 self._y2 *= scaleY
176
177 if self._op != DRAWOP_DRAW_ELLIPTIC_ARC:
178 self._x3 *= scaleX
179 self._y3 *= scaleY
180
181 self._radius *= scaleX
182
183 def Translate(self, x, y):
184 self._x1 += x
185 self._y1 += y
186
187 if self._op == DRAWOP_DRAW_LINE:
188 self._x2 += x
189 self._y2 += y
190 elif self._op == DRAWOP_DRAW_ARC:
191 self._x2 += x
192 self._y2 += y
193 self._x3 += x
194 self._y3 += y
195
196 def Rotate(self, x, y, theta, sinTheta, cosTheta):
197 newX1 = self._x1 * cosTheta + self._y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
198 newY1 = self._x1 * sinTheta + self._y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
199
200 if self._op == DRAWOP_DRAW_LINE:
201 newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
202 newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta;
203
204 self._x1 = newX1
205 self._y1 = newY1
206 self._x2 = newX2
207 self._y2 = newY2
208
209 elif self._op in [DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPTIC_ARC]:
210 # Assume only 0, 90, 180, 270 degree rotations.
211 # oldX1, oldY1 represents the top left corner. Find the
212 # bottom right, and rotate that. Then the width/height is
213 # the difference between x/y values.
214 oldBottomRightX = self._x1 + self._x2
215 oldBottomRightY = self._y1 + self._y2
216 newBottomRightX = oldBottomRightX * cosTheta - oldBottomRightY * sinTheta + x * (1 - cosTheta) + y * sinTheta
217 newBottomRightY = oldBottomRightX * sinTheta + oldBottomRightY * cosTheta + y * (1 - cosTheta) + x * sinTheta
218
219 # Now find the new top-left, bottom-right coordinates.
220 minX = min(newX1, newBottomRightX)
221 minY = min(newY1, newBottomRightY)
222 maxX = max(newX1, newBottomRightX)
223 maxY = max(newY1, newBottomRightY)
224
225 self._x1 = minX
226 self._y1 = minY
227 self._x2 = maxX - minX # width
228 self._y2 = maxY - minY # height
229
230 if self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
231 # Add rotation to angles
232 self._x3 += theta
233 self._y3 += theta
234 elif self._op == DRAWOP_DRAW_ARC:
235 newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
236 newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta
237 newX3 = self._x3 * cosTheta - self._y3 * sinTheta + x * (1 - cosTheta) + y * sinTheta
238 newY3 = self._x3 * sinTheta + self._y3 * cosTheta + y * (1 - cosTheta) + x * sinTheta
239
240 self._x1 = newX1
241 self._y1 = newY1
242 self._x2 = newX2
243 self._y2 = newY2
244 self._x3 = newX3
245 self._y3 = newY3
246
247
248 class OpPolyDraw(DrawOp):
249 """Draw polygon, polyline, spline."""
250 def __init__(self, theOp, thePoints):
251 DrawOp.__init__(self, theOp)
252
253 self._noPoints = len(thePoints)
254 self._points = thePoints
255
256 def Do(self, dc, xoffset, yoffset):
257 if self._op == DRAWOP_DRAW_POLYLINE:
258 dc.DrawLines(self._points, xoffset, yoffset)
259 elif self._op == DRAWOP_DRAW_POLYGON:
260 dc.DrawPolygon(self._points, xoffset, yoffset)
261 elif self._op == DRAWOP_DRAW_SPLINE:
262 dc.DrawSpline(self._points) # no offsets in DrawSpline
263
264 def Scale(self, scaleX, scaleY):
265 for i in range(self._noPoints):
266 self._points[i] = self._points[i][0] * scaleX, self._points[i][1] * scaleY
267
268 def Translate(self, x, y):
269 for i in range(self._noPoints):
270 self._points[i][0] += x
271 self._points[i][1] += y
272
273 def Rotate(self, x, y, theta, sinTheta, cosTheta):
274 for i in range(self._noPoints):
275 x1 = self._points[i][0]
276 y1 = self._points[i][1]
277
278 self._points[i][0] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
279 self._points[i][1] = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
280
281 def OnDrawOutline(self, dc, x, y, w, h, oldW, oldH):
282 dc.SetBrush(wx.TRANSPARENT_BRUSH)
283
284 # Multiply all points by proportion of new size to old size
285 x_proportion = abs(w / oldW)
286 y_proportion = abs(h / oldH)
287
288 dc.DrawPolygon([(x_proportion * x, y_proportion * y) for x, y in self._points], x, y)
289
290 def GetPerimeterPoint(self, x1, y1, x2, y2, xOffset, yOffset, attachmentMode):
291 # First check for situation where the line is vertical,
292 # and we would want to connect to a point on that vertical --
293 # oglFindEndForPolyline can't cope with this (the arrow
294 # gets drawn to the wrong place).
295 if attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
296 # Look for the point we'd be connecting to. This is
297 # a heuristic...
298 for point in self._points:
299 if point[0] == 0:
300 if y2 > y1 and point[1] > 0:
301 return point[0]+xOffset, point[1]+yOffset
302 elif y2 < y1 and point[1] < 0:
303 return point[0]+xOffset, point[1]+yOffset
304
305 return FindEndForPolyline([ p[0] + xOffset for p in self._points ],
306 [ p[1] + yOffset for p in self._points ],
307 x1, y1, x2, y2)
308
309
310 class PseudoMetaFile(object):
311 """
312 A simple metafile-like class which can load data from a Windows
313 metafile on all platforms.
314 """
315 def __init__(self):
316 self._currentRotation = 0
317 self._rotateable = True
318 self._width = 0.0
319 self._height = 0.0
320 self._outlinePen = None
321 self._fillBrush = None
322 self._outlineOp = -1
323
324 self._ops = []
325 self._gdiObjects = []
326
327 self._outlineColours = []
328 self._fillColours = []
329
330 def Clear(self):
331 self._ops = []
332 self._gdiObjects = []
333 self._outlineColours = []
334 self._fillColours = []
335 self._outlineColours = -1
336
337 def IsValid(self):
338 return self._ops != []
339
340 def GetOps(self):
341 return self._ops
342
343 def SetOutlineOp(self, op):
344 self._outlineOp = op
345
346 def GetOutlineOp(self):
347 return self._outlineOp
348
349 def SetOutlinePen(self, pen):
350 self._outlinePen = pen
351
352 def GetOutlinePen(self, pen):
353 return self._outlinePen
354
355 def SetFillBrush(self, brush):
356 self._fillBrush = brush
357
358 def GetFillBrush(self):
359 return self._fillBrush
360
361 def SetSize(self, w, h):
362 self._width = w
363 self._height = h
364
365 def SetRotateable(self, rot):
366 self._rotateable = rot
367
368 def GetRotateable(self):
369 return self._rotateable
370
371 def GetFillColours(self):
372 return self._fillColours
373
374 def GetOutlineColours(self):
375 return self._outlineColours
376
377 def Draw(self, dc, xoffset, yoffset):
378 for op in self._ops:
379 op.Do(dc, xoffset, yoffset)
380
381 def Scale(self, sx, sy):
382 for op in self._ops:
383 op.Scale(sx, sy)
384
385 self._width *= sx
386 self._height *= sy
387
388 def Translate(self, x, y):
389 for op in self._ops:
390 op.Translate(x, y)
391
392 def Rotate(self, x, y, theta):
393 theta1 = theta - self._currentRotation
394 if theta1 == 0:
395 return
396
397 cosTheta = cos(theta1)
398 sinTheta = sin(theta1)
399
400 for op in self._ops:
401 op.Rotate(x, y, theta, sinTheta, cosTheta)
402
403 self._currentRotation = theta
404
405 def LoadFromMetaFile(self, filename, rwidth, rheight):
406 if not os.path.exist(filename):
407 return False
408
409 print "LoadFromMetaFile not implemented yet."
410 return False # TODO
411
412 # Scale to fit size
413 def ScaleTo(self, w, h):
414 scaleX = w / self._width
415 scaleY = h / self._height
416
417 self.Scale(scaleX, scaleY)
418
419 def GetBounds(self):
420 maxX, maxY, minX, minY = -99999.9, -99999.9, 99999.9, 99999.9
421
422 for op in self._ops:
423 if op.GetOp() in [DRAWOP_DRAW_LINE, DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE, DRAWOP_DRAW_POINT, DRAWOP_DRAW_TEXT]:
424 if op._x1 < minX:
425 minX = op._x1
426 if op._x1 > maxX:
427 maxX = op._x1
428 if op._y1 < minY:
429 minY = op._y1
430 if op._y1 > maxY:
431 maxY = op._y1
432 if op.GetOp() == DRAWOP_DRAW_LINE:
433 if op._x2 < minX:
434 minX = op._x2
435 if op._x2 > maxX:
436 maxX = op._x2
437 if op._y2 < minY:
438 minY = op._y2
439 if op._y2 > maxY:
440 maxY = op._y2
441 elif op.GetOp() in [ DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE]:
442 if op._x1 + op._x2 < minX:
443 minX = op._x1 + op._x2
444 if op._x1 + op._x2 > maxX:
445 maxX = op._x1 + op._x2
446 if op._y1 + op._y2 < minY:
447 minY = op._y1 + op._y2
448 if op._y1 + op._y2 > maxX:
449 maxY = op._y1 + op._y2
450 elif op.GetOp() == DRAWOP_DRAW_ARC:
451 # TODO: don't yet know how to calculate the bounding box
452 # for an arc. So pretend it's a line; to get a correct
453 # bounding box, draw a blank rectangle first, of the
454 # correct size.
455 if op._x1 < minX:
456 minX = op._x1
457 if op._x1 > maxX:
458 maxX = op._x1
459 if op._y1 < minY:
460 minY = op._y1
461 if op._y1 > maxY:
462 maxY = op._y1
463 if op._x2 < minX:
464 minX = op._x2
465 if op._x2 > maxX:
466 maxX = op._x2
467 if op._y2 < minY:
468 minY = op._y2
469 if op._y2 > maxY:
470 maxY = op._y2
471 elif op.GetOp() in [DRAWOP_DRAW_POLYLINE, DRAWOP_DRAW_POLYGON, DRAWOP_DRAW_SPLINE]:
472 for point in op._points:
473 if point[0] < minX:
474 minX = point[0]
475 if point[0] > maxX:
476 maxX = point[0]
477 if point[1] < minY:
478 minY = point[1]
479 if point[1] > maxY:
480 maxY = point[1]
481
482 return [minX, minY, maxX, maxY]
483
484 # Calculate size from current operations
485 def CalculateSize(self, shape):
486 boundMinX, boundMinY, boundMaxX, boundMaxY = self.GetBounds()
487
488 # By Pierre Hjälm: This is NOT in the old version, which
489 # gets this totally wrong. Since the drawing is centered, we
490 # cannot get the width by measuring from left to right, we
491 # must instead make enough room to handle the largest
492 # coordinates
493 #self.SetSize(boundMaxX - boundMinX, boundMaxY - boundMinY)
494
495 w = max(abs(boundMinX), abs(boundMaxX)) * 2
496 h = max(abs(boundMinY), abs(boundMaxY)) * 2
497
498 self.SetSize(w, h)
499
500 if shape:
501 shape.SetWidth(self._width)
502 shape.SetHeight(self._height)
503
504 # Set of functions for drawing into a pseudo metafile
505 def DrawLine(self, pt1, pt2):
506 op = OpDraw(DRAWOP_DRAW_LINE, pt1[0], pt1[1], pt2[0], pt2[1])
507 self._ops.append(op)
508
509 def DrawRectangle(self, rect):
510 op = OpDraw(DRAWOP_DRAW_RECT, rect[0], rect[1], rect[2], rect[3])
511 self._ops.append(op)
512
513 def DrawRoundedRectangle(self, rect, radius):
514 op = OpDraw(DRAWOP_DRAW_ROUNDED_RECT, rect[0], rect[1], rect[2], rect[3])
515 op._radius = radius
516 self._ops.append(op)
517
518 def DrawEllipse(self, rect):
519 op = OpDraw(DRAWOP_DRAW_ELLIPSE, rect[0], rect[1], rect[2], rect[3])
520 self._ops.append(op)
521
522 def DrawArc(self, centrePt, startPt, endPt):
523 op = OpDraw(DRAWOP_DRAW_ARC, centrePt[0], centrePt[1], startPt[0], startPt[1])
524 op._x3, op._y3 = endPt
525
526 self._ops.append(op)
527
528 def DrawEllipticArc(self, rect, startAngle, endAngle):
529 startAngleRadians = startAngle * math.pi * 2 / 360
530 endAngleRadians = endAngle * math.pi * 2 / 360
531
532 op = OpDraw(DRAWOP_DRAW_ELLIPTIC_ARC, rect[0], rect[1], rect[2], rect[3])
533 op._x3 = startAngleRadians
534 op._y3 = endAngleRadians
535
536 self._ops.append(op)
537
538 def DrawPoint(self, pt):
539 op = OpDraw(DRAWOP_DRAW_POINT, pt[0], pt[1], 0, 0)
540 self._ops.append(op)
541
542 def DrawText(self, text, pt):
543 op = OpDraw(DRAWOP_DRAW_TEXT, pt[0], pt[1], 0, 0)
544 op._textString = text
545 self._ops.append(op)
546
547 def DrawLines(self, pts):
548 op = OpPolyDraw(DRAWOP_DRAW_POLYLINE, pts)
549 self._ops.append(op)
550
551 # flags:
552 # oglMETAFLAGS_OUTLINE: will be used for drawing the outline and
553 # also drawing lines/arrows at the circumference.
554 # oglMETAFLAGS_ATTACHMENTS: will be used for initialising attachment
555 # points at the vertices (perhaps a rare case...)
556 def DrawPolygon(self, pts, flags = 0):
557 op = OpPolyDraw(DRAWOP_DRAW_POLYGON, pts)
558 self._ops.append(op)
559
560 if flags & METAFLAGS_OUTLINE:
561 self._outlineOp = len(self._ops) - 1
562
563 def DrawSpline(self, pts):
564 op = OpPolyDraw(DRAWOP_DRAW_SPLINE, pts)
565 self._ops.append(op)
566
567 def SetClippingRect(self, rect):
568 OpSetClipping(DRAWOP_SET_CLIPPING_RECT, rect[0], rect[1], rect[2], rect[3])
569
570 def DestroyClippingRect(self):
571 op = OpSetClipping(DRAWOP_DESTROY_CLIPPING_RECT, 0, 0, 0, 0)
572 self._ops.append(op)
573
574 def SetPen(self, pen, isOutline = False):
575 self._gdiObjects.append(pen)
576 op = OpSetGDI(DRAWOP_SET_PEN, self, len(self._gdiObjects) - 1)
577 self._ops.append(op)
578
579 if isOutline:
580 self._outlineColours.append(len(self._gdiObjects) - 1)
581
582 def SetBrush(self, brush, isFill = False):
583 self._gdiObjects.append(brush)
584 op = OpSetGDI(DRAWOP_SET_BRUSH, self, len(self._gdiObjects) - 1)
585 self._ops.append(op)
586
587 if isFill:
588 self._fillColours.append(len(self._gdiObjects) - 1)
589
590 def SetFont(self, font):
591 self._gdiObjects.append(font)
592 op = OpSetGDI(DRAWOP_SET_FONT, self, len(self._gdiObjects) - 1)
593 self._ops.append(op)
594
595 def SetTextColour(self, colour):
596 op = OpSetGDI(DRAWOP_SET_TEXT_COLOUR, self, 0)
597 op._r, op._g, op._b = colour.Red(), colour.Green(), colour.Blue()
598
599 self._ops.append(op)
600
601 def SetBackgroundColour(self, colour):
602 op = OpSetGDI(DRAWOP_SET_BK_COLOUR, self, 0)
603 op._r, op._g, op._b = colour.Red(), colour.Green(), colour.Blue()
604
605 self._ops.append(op)
606
607 def SetBackgroundMode(self, mode):
608 op = OpSetGDI(DRAWOP_SET_BK_MODE, self, 0)
609 self._ops.append(op)
610
611 class DrawnShape(RectangleShape):
612 """
613 Draws a pseudo-metafile shape, which can be loaded from a simple
614 Windows metafile.
615
616 wxDrawnShape allows you to specify a different shape for each of four
617 orientations (North, West, South and East). It also provides a set of
618 drawing functions for programmatic drawing of a shape, so that during
619 construction of the shape you can draw into it as if it were a device
620 context.
621
622 Derived from:
623 RectangleShape
624 """
625 def __init__(self):
626 RectangleShape.__init__(self, 100, 50)
627 self._saveToFile = True
628 self._currentAngle = DRAWN_ANGLE_0
629
630 self._metafiles=PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile()
631
632 def OnDraw(self, dc):
633 # Pass pen and brush in case we have force outline
634 # and fill colours
635 if self._shadowMode != SHADOW_NONE:
636 if self._shadowBrush:
637 self._metafiles[self._currentAngle]._fillBrush = self._shadowBrush
638 self._metafiles[self._currentAngle]._outlinePen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)
639 self._metafiles[self._currentAngle].Draw(dc, self._xpos + self._shadowOffsetX, self._ypos + self._shadowOffsetY)
640
641 self._metafiles[self._currentAngle]._outlinePen = self._pen
642 self._metafiles[self._currentAngle]._fillBrush = self._brush
643 self._metafiles[self._currentAngle].Draw(dc, self._xpos, self._ypos)
644
645 def SetSize(self, w, h, recursive = True):
646 self.SetAttachmentSize(w, h)
647
648 if self.GetWidth() == 0.0:
649 scaleX = 1
650 else:
651 scaleX = w / self.GetWidth()
652
653 if self.GetHeight() == 0.0:
654 scaleY = 1
655 else:
656 scaleY = h / self.GetHeight()
657
658 for i in range(4):
659 if self._metafiles[i].IsValid():
660 self._metafiles[i].Scale(scaleX, scaleY)
661
662 self._width = w
663 self._height = h
664 self.SetDefaultRegionSize()
665
666 def Scale(self, sx, sy):
667 """Scale the shape by the given amount."""
668 for i in range(4):
669 if self._metafiles[i].IsValid():
670 self._metafiles[i].Scale(sx, sy)
671 self._metafiles[i].CalculateSize(self)
672
673 def Translate(self, x, y):
674 """Translate the shape by the given amount."""
675 for i in range(4):
676 if self._metafiles[i].IsValid():
677 self._metafiles[i].Translate(x, y)
678 self._metafiles[i].CalculateSize(self)
679
680 # theta is absolute rotation from the zero position
681 def Rotate(self, x, y, theta):
682 """Rotate about the given axis by the given amount in radians."""
683 self._currentAngle = self.DetermineMetaFile(theta)
684
685 if self._currentAngle == 0:
686 # Rotate metafile
687 if not self._metafiles[0].GetRotateable():
688 return
689
690 self._metafiles[0].Rotate(x, y, theta)
691
692 actualTheta = theta - self._rotation
693
694 # Rotate attachment points
695 sinTheta = math.sin(actualTheta)
696 cosTheta = math.cos(actualTheta)
697
698 for point in self._attachmentPoints:
699 x1 = point._x
700 y1 = point._y
701
702 point._x = x1 * cosTheta - y1 * sinTheta + x * (1.0 - cosTheta) + y * sinTheta
703 point._y = x1 * sinTheta + y1 * cosTheta + y * (1.0 - cosTheta) + x * sinTheta
704
705 self._rotation = theta
706
707 self._metafiles[self._currentAngle].CalculateSize(self)
708
709 # Which metafile do we use now? Based on current rotation and validity
710 # of metafiles.
711 def DetermineMetaFile(self, rotation):
712 tolerance = 0.0001
713 angles = [0.0, math.pi / 2, math.pi, 3 * pi / 2]
714
715 whichMetaFile = 0
716
717 for i in range(4):
718 if RoughlyEqual(rotation, angles[i], tolerance):
719 whichMetaFile = i
720 break
721
722 if whichMetaFile > 0 and not self._metafiles[whichMetaFile].IsValid():
723 whichMetaFile = 0
724
725 return whichMetaFile
726
727 def OnDrawOutline(self, dc, x, y, w, h):
728 if self._metafiles[self._currentAngle].GetOutlineOp() != -1:
729 op = self._metafiles[self._currentAngle].GetOps()[self._metafiles[self._currentAngle].GetOutlineOp()]
730 if op.OnDrawOutline(dc, x, y, w, h, self._width, self._height):
731 return
732
733 # Default... just use a rectangle
734 RectangleShape.OnDrawOutline(self, dc, x, y, w, h)
735
736 # Get the perimeter point using the special outline op, if there is one,
737 # otherwise use default wxRectangleShape scheme
738 def GetPerimeterPoint(self, x1, y1, x2, y2):
739 if self._metafiles[self._currentAngle].GetOutlineOp() != -1:
740 op = self._metafiles[self._currentAngle].GetOps()[self._metafiles[self._currentAngle].GetOutlineOp()]
741 p = op.GetPerimeterPoint(x1, y1, x2, y2, self.GetX(), self.GetY(), self.GetAttachmentMode())
742 if p:
743 return p
744
745 return RectangleShape.GetPerimeterPoint(self, x1, y1, x2, y2)
746
747 def LoadFromMetaFile(self, filename):
748 """Load a (very simple) Windows metafile, created for example by
749 Top Draw, the Windows shareware graphics package."""
750 return self._metafiles[0].LoadFromMetaFile(filename)
751
752 # Set of functions for drawing into a pseudo metafile.
753 # They use integers, but doubles are used internally for accuracy
754 # when scaling.
755 def DrawLine(self, pt1, pt2):
756 self._metafiles[self._currentAngle].DrawLine(pt1, pt2)
757
758 def DrawRectangle(self, rect):
759 self._metafiles[self._currentAngle].DrawRectangle(rect)
760
761 def DrawRoundedRectangle(self, rect, radius):
762 """Draw a rounded rectangle.
763
764 radius is the corner radius. If radius is negative, it expresses
765 the radius as a proportion of the smallest dimension of the rectangle.
766 """
767 self._metafiles[self._currentAngle].DrawRoundedRectangle(rect, radius)
768
769 def DrawEllipse(self, rect):
770 self._metafiles[self._currentAngle].DrawEllipse(rect)
771
772 def DrawArc(self, centrePt, startPt, endPt):
773 """Draw an arc."""
774 self._metafiles[self._currentAngle].DrawArc(centrePt, startPt, endPt)
775
776 def DrawEllipticArc(self, rect, startAngle, endAngle):
777 """Draw an elliptic arc."""
778 self._metafiles[self._currentAngle].DrawEllipticArc(rect, startAngle, endAngle)
779
780 def DrawPoint(self, pt):
781 self._metafiles[self._currentAngle].DrawPoint(pt)
782
783 def DrawText(self, text, pt):
784 self._metafiles[self._currentAngle].DrawText(text, pt)
785
786 def DrawLines(self, pts):
787 self._metafiles[self._currentAngle].DrawLines(pts)
788
789 def DrawPolygon(self, pts, flags = 0):
790 """Draw a polygon.
791
792 flags can be one or more of:
793 METAFLAGS_OUTLINE (use this polygon for the drag outline) and
794 METAFLAGS_ATTACHMENTS (use the vertices of this polygon for attachments).
795 """
796 if flags and METAFLAGS_ATTACHMENTS:
797 self.ClearAttachments()
798 for i in range(len(pts)):
799 self._attachmentPoints.append(AttachmentPoint(i,pts[i][0],pts[i][1]))
800 self._metafiles[self._currentAngle].DrawPolygon(pts, flags)
801
802 def DrawSpline(self, pts):
803 self._metafiles[self._currentAngle].DrawSpline(pts)
804
805 def SetClippingRect(self, rect):
806 """Set the clipping rectangle."""
807 self._metafiles[self._currentAngle].SetClippingRect(rect)
808
809 def DestroyClippingRect(self):
810 """Destroy the clipping rectangle."""
811 self._metafiles[self._currentAngle].DestroyClippingRect()
812
813 def SetDrawnPen(self, pen, isOutline = False):
814 """Set the pen for this metafile.
815
816 If isOutline is True, this pen is taken to indicate the outline
817 (and if the outline pen is changed for the whole shape, the pen
818 will be replaced with the outline pen).
819 """
820 self._metafiles[self._currentAngle].SetPen(pen, isOutline)
821
822 def SetDrawnBrush(self, brush, isFill = False):
823 """Set the brush for this metafile.
824
825 If isFill is True, the brush is used as the fill brush.
826 """
827 self._metafiles[self._currentAngle].SetBrush(brush, isFill)
828
829 def SetDrawnFont(self, font):
830 self._metafiles[self._currentAngle].SetFont(font)
831
832 def SetDrawnTextColour(self, colour):
833 """Set the current text colour for the current metafile."""
834 self._metafiles[self._currentAngle].SetTextColour(colour)
835
836 def SetDrawnBackgroundColour(self, colour):
837 """Set the current background colour for the current metafile."""
838 self._metafiles[self._currentAngle].SetBackgroundColour(colour)
839
840 def SetDrawnBackgroundMode(self, mode):
841 """Set the current background mode for the current metafile."""
842 self._metafiles[self._currentAngle].SetBackgroundMode(mode)
843
844 def CalculateSize(self):
845 """Calculate the wxDrawnShape size from the current metafile.
846
847 Call this after you have drawn into the shape.
848 """
849 self._metafiles[self._currentAngle].CalculateSize(self)
850
851 def DrawAtAngle(self, angle):
852 """Set the metafile for the given orientation, which can be one of:
853
854 * DRAWN_ANGLE_0
855 * DRAWN_ANGLE_90
856 * DRAWN_ANGLE_180
857 * DRAWN_ANGLE_270
858 """
859 self._currentAngle = angle
860
861 def GetAngle(self):
862 """Return the current orientation, which can be one of:
863
864 * DRAWN_ANGLE_0
865 * DRAWN_ANGLE_90
866 * DRAWN_ANGLE_180
867 * DRAWN_ANGLE_270
868 """
869 return self._currentAngle
870
871 def GetRotation(self):
872 """Return the current rotation of the shape in radians."""
873 return self._rotation
874
875 def SetSaveToFile(self, save):
876 """If save is True, the image will be saved along with the shape's
877 other attributes. The reason why this might not be desirable is that
878 if there are many shapes with the same image, it would be more
879 efficient for the application to save one copy, and not duplicate
880 the information for every shape. The default is True.
881 """
882 self._saveToFile = save
883
884 def GetMetaFile(self, which = 0):
885 """Return a reference to the internal 'pseudo-metafile'."""
886 return self._metafiles[which]