]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_drawn.py
Patch from Andrea that fixes the following problems/issues:
[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 def Rotate(self, x, y, theta, sinTheta, cosTheta):
66 pass
67
68 class OpSetGDI(DrawOp):
69 """Set font, brush, text colour."""
70 def __init__(self, theOp, theImage, theGdiIndex, theMode = 0):
71 DrawOp.__init__(self, theOp)
72
73 self._gdiIndex = theGdiIndex
74 self._image = theImage
75 self._mode = theMode
76
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)
83 else:
84 try:
85 dc.SetPen(self._image._gdiObjects[self._gdiIndex])
86 except IndexError:
87 pass
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)
94 if br:
95 dc.SetBrush(br)
96 elif self._gdiIndex in self._image._fillColours:
97 if self._image._fillBrush:
98 dc.SetBrush(self._image._fillBrush)
99 else:
100 brush = self._image._gdiObjects[self._gdiIndex]
101 if brush:
102 dc.SetBrush(brush)
103 elif self._op == DRAWOP_SET_FONT:
104 try:
105 dc.SetFont(self._image._gdiObjects[self._gdiIndex])
106 except IndexError:
107 pass
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)
114
115
116 class OpSetClipping(DrawOp):
117 """Set/destroy clipping."""
118 def __init__(self, theOp, theX1, theY1, theX2, theY2):
119 DrawOp.__init__(self, theOp)
120
121 self._x1 = theX1
122 self._y1 = theY1
123 self._x2 = theX2
124 self._y2 = theY2
125
126 def Do(self, dc, xoffset, yoffset):
127 if self._op == DRAWOP_SET_CLIPPING_RECT:
128 dc.SetClippingRegion(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
129 elif self._op == DRAWOP_DESTROY_CLIPPING_RECT:
130 dc.DestroyClippingRegion()
131
132 def Scale(self, scaleX, scaleY):
133 self._x1 *= scaleX
134 self._y1 *= scaleY
135 self._x2 *= scaleX
136 self._y2 *= scaleY
137
138 def Translate(self, x, y):
139 self._x1 += x
140 self._y1 += y
141
142
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)
147
148 self._x1 = theX1
149 self._y1 = theY1
150 self._x2 = theX2
151 self._y2 = theY2
152 self._x3 = 0.0
153 self._y3 = 0.0
154 self._radius = theRadius
155 self._textString = s
156
157 def Do(self, dc, xoffset, yoffset):
158 if self._op == DRAWOP_DRAW_LINE:
159 dc.DrawLine(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
160 elif self._op == DRAWOP_DRAW_RECT:
161 dc.DrawRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
162 elif self._op == DRAWOP_DRAW_ROUNDED_RECT:
163 dc.DrawRoundedRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._radius)
164 elif self._op == DRAWOP_DRAW_ELLIPSE:
165 dc.DrawEllipse(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
166 elif self._op == DRAWOP_DRAW_ARC:
167 dc.DrawArc(self._x2 + xoffset, self._y2 + yoffset, self._x3 + xoffset, self._y3 + yoffset, self._x1 + xoffset, self._y1 + yoffset)
168 elif self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
169 dc.DrawEllipticArc(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._x3 * 360 / (2 * math.pi), self._y3 * 360 / (2 * math.pi))
170 elif self._op == DRAWOP_DRAW_POINT:
171 dc.DrawPoint(self._x1 + xoffset, self._y1 + yoffset)
172 elif self._op == DRAWOP_DRAW_TEXT:
173 dc.DrawText(self._textString, self._x1 + xoffset, self._y1 + yoffset)
174 def Scale(self, scaleX, scaleY):
175 self._x1 *= scaleX
176 self._y1 *= scaleY
177 self._x2 *= scaleX
178 self._y2 *= scaleY
179
180 if self._op != DRAWOP_DRAW_ELLIPTIC_ARC:
181 self._x3 *= scaleX
182 self._y3 *= scaleY
183
184 self._radius *= scaleX
185
186 def Translate(self, x, y):
187 self._x1 += x
188 self._y1 += y
189
190 if self._op == DRAWOP_DRAW_LINE:
191 self._x2 += x
192 self._y2 += y
193 elif self._op == DRAWOP_DRAW_ARC:
194 self._x2 += x
195 self._y2 += y
196 self._x3 += x
197 self._y3 += y
198
199 def Rotate(self, x, y, theta, sinTheta, cosTheta):
200 newX1 = self._x1 * cosTheta + self._y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
201 newY1 = self._x1 * sinTheta + self._y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
202
203 if self._op == DRAWOP_DRAW_LINE:
204 newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
205 newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta;
206
207 self._x1 = newX1
208 self._y1 = newY1
209 self._x2 = newX2
210 self._y2 = newY2
211
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._x1 + self._x2
218 oldBottomRightY = self._y1 + self._y2
219 newBottomRightX = oldBottomRightX * cosTheta - oldBottomRightY * sinTheta + x * (1 - cosTheta) + y * sinTheta
220 newBottomRightY = oldBottomRightX * sinTheta + oldBottomRightY * cosTheta + y * (1 - cosTheta) + x * sinTheta
221
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)
227
228 self._x1 = minX
229 self._y1 = minY
230 self._x2 = maxX - minX # width
231 self._y2 = maxY - minY # height
232
233 if self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
234 # Add rotation to angles
235 self._x3 += theta
236 self._y3 += theta
237 elif self._op == DRAWOP_DRAW_ARC:
238 newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
239 newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta
240 newX3 = self._x3 * cosTheta - self._y3 * sinTheta + x * (1 - cosTheta) + y * sinTheta
241 newY3 = self._x3 * sinTheta + self._y3 * cosTheta + y * (1 - cosTheta) + x * sinTheta
242
243 self._x1 = newX1
244 self._y1 = newY1
245 self._x2 = newX2
246 self._y2 = newY2
247 self._x3 = newX3
248 self._y3 = newY3
249
250
251 class OpPolyDraw(DrawOp):
252 """Draw polygon, polyline, spline."""
253 def __init__(self, theOp, thePoints):
254 DrawOp.__init__(self, theOp)
255
256 self._noPoints = len(thePoints)
257 self._points = thePoints
258
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
266
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)
270
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
275
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]
280
281 self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
282
283 def OnDrawOutline(self, dc, x, y, w, h, oldW, oldH):
284 dc.SetBrush(wx.TRANSPARENT_BRUSH)
285
286 # Multiply all points by proportion of new size to old size
287 x_proportion = abs(w / oldW)
288 y_proportion = abs(h / oldH)
289
290 dc.DrawPolygon([(x_proportion * x, y_proportion * y) for x, y in self._points], x, y)
291
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
299 # a heuristic...
300 for point in self._points:
301 if point[0] == 0:
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
306
307 return FindEndForPolyline([ p[0] + xOffset for p in self._points ],
308 [ p[1] + yOffset for p in self._points ],
309 x1, y1, x2, y2)
310
311
312 class PseudoMetaFile(object):
313 """
314 A simple metafile-like class which can load data from a Windows
315 metafile on all platforms.
316 """
317 def __init__(self):
318 self._currentRotation = 0
319 self._rotateable = True
320 self._width = 0.0
321 self._height = 0.0
322 self._outlinePen = None
323 self._fillBrush = None
324 self._outlineOp = -1
325
326 self._ops = []
327 self._gdiObjects = []
328
329 self._outlineColours = []
330 self._fillColours = []
331
332 def Clear(self):
333 self._ops = []
334 self._gdiObjects = []
335 self._outlineColours = []
336 self._fillColours = []
337 self._outlineColours = -1
338
339 def IsValid(self):
340 return self._ops != []
341
342 def GetOps(self):
343 return self._ops
344
345 def SetOutlineOp(self, op):
346 self._outlineOp = op
347
348 def GetOutlineOp(self):
349 return self._outlineOp
350
351 def SetOutlinePen(self, pen):
352 self._outlinePen = pen
353
354 def GetOutlinePen(self, pen):
355 return self._outlinePen
356
357 def SetFillBrush(self, brush):
358 self._fillBrush = brush
359
360 def GetFillBrush(self):
361 return self._fillBrush
362
363 def SetSize(self, w, h):
364 self._width = w
365 self._height = h
366
367 def SetRotateable(self, rot):
368 self._rotateable = rot
369
370 def GetRotateable(self):
371 return self._rotateable
372
373 def GetFillColours(self):
374 return self._fillColours
375
376 def GetOutlineColours(self):
377 return self._outlineColours
378
379 def Draw(self, dc, xoffset, yoffset):
380 for op in self._ops:
381 op.Do(dc, xoffset, yoffset)
382
383 def Scale(self, sx, sy):
384 for op in self._ops:
385 op.Scale(sx, sy)
386
387 self._width *= sx
388 self._height *= sy
389
390 def Translate(self, x, y):
391 for op in self._ops:
392 op.Translate(x, y)
393
394 def Rotate(self, x, y, theta):
395 theta1 = theta - self._currentRotation
396 if theta1 == 0:
397 return
398
399 cosTheta = math.cos(theta1)
400 sinTheta = math.sin(theta1)
401
402 for op in self._ops:
403 op.Rotate(x, y, theta, sinTheta, cosTheta)
404
405 self._currentRotation = theta
406
407 def LoadFromMetaFile(self, filename, rwidth, rheight):
408 if not os.path.exist(filename):
409 return False
410
411 print "LoadFromMetaFile not implemented yet."
412 return False # TODO
413
414 # Scale to fit size
415 def ScaleTo(self, w, h):
416 scaleX = w / self._width
417 scaleY = h / self._height
418
419 self.Scale(scaleX, scaleY)
420
421 def GetBounds(self):
422 maxX, maxY, minX, minY = -99999.9, -99999.9, 99999.9, 99999.9
423
424 for op in self._ops:
425 if op.GetOp() in [DRAWOP_DRAW_LINE, DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE, DRAWOP_DRAW_POINT, DRAWOP_DRAW_TEXT]:
426 if op._x1 < minX:
427 minX = op._x1
428 if op._x1 > maxX:
429 maxX = op._x1
430 if op._y1 < minY:
431 minY = op._y1
432 if op._y1 > maxY:
433 maxY = op._y1
434 if op.GetOp() == DRAWOP_DRAW_LINE:
435 if op._x2 < minX:
436 minX = op._x2
437 if op._x2 > maxX:
438 maxX = op._x2
439 if op._y2 < minY:
440 minY = op._y2
441 if op._y2 > maxY:
442 maxY = op._y2
443 elif op.GetOp() in [ DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE]:
444 if op._x1 + op._x2 < minX:
445 minX = op._x1 + op._x2
446 if op._x1 + op._x2 > maxX:
447 maxX = op._x1 + op._x2
448 if op._y1 + op._y2 < minY:
449 minY = op._y1 + op._y2
450 if op._y1 + op._y2 > maxX:
451 maxY = op._y1 + op._y2
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
456 # correct size.
457 if op._x1 < minX:
458 minX = op._x1
459 if op._x1 > maxX:
460 maxX = op._x1
461 if op._y1 < minY:
462 minY = op._y1
463 if op._y1 > maxY:
464 maxY = op._y1
465 if op._x2 < minX:
466 minX = op._x2
467 if op._x2 > maxX:
468 maxX = op._x2
469 if op._y2 < minY:
470 minY = op._y2
471 if op._y2 > maxY:
472 maxY = op._y2
473 elif op.GetOp() in [DRAWOP_DRAW_POLYLINE, DRAWOP_DRAW_POLYGON, DRAWOP_DRAW_SPLINE]:
474 for point in op._points:
475 if point[0] < minX:
476 minX = point[0]
477 if point[0] > maxX:
478 maxX = point[0]
479 if point[1] < minY:
480 minY = point[1]
481 if point[1] > maxY:
482 maxY = point[1]
483
484 return [minX, minY, maxX, maxY]
485
486 # Calculate size from current operations
487 def CalculateSize(self, shape):
488 boundMinX, boundMinY, boundMaxX, boundMaxY = self.GetBounds()
489
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
494 # coordinates
495 #self.SetSize(boundMaxX - boundMinX, boundMaxY - boundMinY)
496
497 w = max(abs(boundMinX), abs(boundMaxX)) * 2
498 h = max(abs(boundMinY), abs(boundMaxY)) * 2
499
500 self.SetSize(w, h)
501
502 if shape:
503 shape.SetWidth(self._width)
504 shape.SetHeight(self._height)
505
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])
509 self._ops.append(op)
510
511 def DrawRectangle(self, rect):
512 op = OpDraw(DRAWOP_DRAW_RECT, rect[0], rect[1], rect[2], rect[3])
513 self._ops.append(op)
514
515 def DrawRoundedRectangle(self, rect, radius):
516 op = OpDraw(DRAWOP_DRAW_ROUNDED_RECT, rect[0], rect[1], rect[2], rect[3])
517 op._radius = radius
518 self._ops.append(op)
519
520 def DrawEllipse(self, rect):
521 op = OpDraw(DRAWOP_DRAW_ELLIPSE, rect[0], rect[1], rect[2], rect[3])
522 self._ops.append(op)
523
524 def DrawArc(self, centrePt, startPt, endPt):
525 op = OpDraw(DRAWOP_DRAW_ARC, centrePt[0], centrePt[1], startPt[0], startPt[1])
526 op._x3, op._y3 = endPt
527
528 self._ops.append(op)
529
530 def DrawEllipticArc(self, rect, startAngle, endAngle):
531 startAngleRadians = startAngle * math.pi * 2 / 360
532 endAngleRadians = endAngle * math.pi * 2 / 360
533
534 op = OpDraw(DRAWOP_DRAW_ELLIPTIC_ARC, rect[0], rect[1], rect[2], rect[3])
535 op._x3 = startAngleRadians
536 op._y3 = endAngleRadians
537
538 self._ops.append(op)
539
540 def DrawPoint(self, pt):
541 op = OpDraw(DRAWOP_DRAW_POINT, pt[0], pt[1], 0, 0)
542 self._ops.append(op)
543
544 def DrawText(self, text, pt):
545 op = OpDraw(DRAWOP_DRAW_TEXT, pt[0], pt[1], 0, 0)
546 op._textString = text
547 self._ops.append(op)
548
549 def DrawLines(self, pts):
550 op = OpPolyDraw(DRAWOP_DRAW_POLYLINE, pts)
551 self._ops.append(op)
552
553 # flags:
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)
560 self._ops.append(op)
561
562 if flags & METAFLAGS_OUTLINE:
563 self._outlineOp = len(self._ops) - 1
564
565 def DrawSpline(self, pts):
566 op = OpPolyDraw(DRAWOP_DRAW_SPLINE, pts)
567 self._ops.append(op)
568
569 def SetClippingRect(self, rect):
570 OpSetClipping(DRAWOP_SET_CLIPPING_RECT, rect[0], rect[1], rect[2], rect[3])
571
572 def DestroyClippingRect(self):
573 op = OpSetClipping(DRAWOP_DESTROY_CLIPPING_RECT, 0, 0, 0, 0)
574 self._ops.append(op)
575
576 def SetPen(self, pen, isOutline = False):
577 self._gdiObjects.append(pen)
578 op = OpSetGDI(DRAWOP_SET_PEN, self, len(self._gdiObjects) - 1)
579 self._ops.append(op)
580
581 if isOutline:
582 self._outlineColours.append(len(self._gdiObjects) - 1)
583
584 def SetBrush(self, brush, isFill = False):
585 self._gdiObjects.append(brush)
586 op = OpSetGDI(DRAWOP_SET_BRUSH, self, len(self._gdiObjects) - 1)
587 self._ops.append(op)
588
589 if isFill:
590 self._fillColours.append(len(self._gdiObjects) - 1)
591
592 def SetFont(self, font):
593 self._gdiObjects.append(font)
594 op = OpSetGDI(DRAWOP_SET_FONT, self, len(self._gdiObjects) - 1)
595 self._ops.append(op)
596
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()
600
601 self._ops.append(op)
602
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()
606
607 self._ops.append(op)
608
609 def SetBackgroundMode(self, mode):
610 op = OpSetGDI(DRAWOP_SET_BK_MODE, self, 0)
611 self._ops.append(op)
612
613 class DrawnShape(RectangleShape):
614 """
615 Draws a pseudo-metafile shape, which can be loaded from a simple
616 Windows metafile.
617
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
622 context.
623
624 Derived from:
625 RectangleShape
626 """
627 def __init__(self):
628 RectangleShape.__init__(self, 100, 50)
629 self._saveToFile = True
630 self._currentAngle = DRAWN_ANGLE_0
631
632 self._metafiles=PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile()
633
634 def OnDraw(self, dc):
635 # Pass pen and brush in case we have force outline
636 # and fill colours
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)
642
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)
646
647 def SetSize(self, w, h, recursive = True):
648 self.SetAttachmentSize(w, h)
649
650 if self.GetWidth() == 0.0:
651 scaleX = 1
652 else:
653 scaleX = w / self.GetWidth()
654
655 if self.GetHeight() == 0.0:
656 scaleY = 1
657 else:
658 scaleY = h / self.GetHeight()
659
660 for i in range(4):
661 if self._metafiles[i].IsValid():
662 self._metafiles[i].Scale(scaleX, scaleY)
663
664 self._width = w
665 self._height = h
666 self.SetDefaultRegionSize()
667
668 def Scale(self, sx, sy):
669 """Scale the shape by the given amount."""
670 for i in range(4):
671 if self._metafiles[i].IsValid():
672 self._metafiles[i].Scale(sx, sy)
673 self._metafiles[i].CalculateSize(self)
674
675 def Translate(self, x, y):
676 """Translate the shape by the given amount."""
677 for i in range(4):
678 if self._metafiles[i].IsValid():
679 self._metafiles[i].Translate(x, y)
680 self._metafiles[i].CalculateSize(self)
681
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)
686
687 if self._currentAngle == 0:
688 # Rotate metafile
689 if not self._metafiles[0].GetRotateable():
690 return
691
692 self._metafiles[0].Rotate(x, y, theta)
693
694 actualTheta = theta - self._rotation
695
696 # Rotate attachment points
697 sinTheta = math.sin(actualTheta)
698 cosTheta = math.cos(actualTheta)
699
700 for point in self._attachmentPoints:
701 x1 = point._x
702 y1 = point._y
703
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
706
707 self._rotation = theta
708
709 self._metafiles[self._currentAngle].CalculateSize(self)
710
711 # Which metafile do we use now? Based on current rotation and validity
712 # of metafiles.
713 def DetermineMetaFile(self, rotation):
714 tolerance = 0.0001
715 angles = [0.0, math.pi / 2, math.pi, 3 * math.pi / 2]
716
717 whichMetaFile = 0
718
719 for i in range(4):
720 if RoughlyEqual(rotation, angles[i], tolerance):
721 whichMetaFile = i
722 break
723
724 if whichMetaFile > 0 and not self._metafiles[whichMetaFile].IsValid():
725 whichMetaFile = 0
726
727 return whichMetaFile
728
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):
733 return
734
735 # Default... just use a rectangle
736 RectangleShape.OnDrawOutline(self, dc, x, y, w, h)
737
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())
744 if p:
745 return p
746
747 return RectangleShape.GetPerimeterPoint(self, x1, y1, x2, y2)
748
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)
753
754 # Set of functions for drawing into a pseudo metafile.
755 # They use integers, but doubles are used internally for accuracy
756 # when scaling.
757 def DrawLine(self, pt1, pt2):
758 self._metafiles[self._currentAngle].DrawLine(pt1, pt2)
759
760 def DrawRectangle(self, rect):
761 self._metafiles[self._currentAngle].DrawRectangle(rect)
762
763 def DrawRoundedRectangle(self, rect, radius):
764 """Draw a rounded rectangle.
765
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.
768 """
769 self._metafiles[self._currentAngle].DrawRoundedRectangle(rect, radius)
770
771 def DrawEllipse(self, rect):
772 self._metafiles[self._currentAngle].DrawEllipse(rect)
773
774 def DrawArc(self, centrePt, startPt, endPt):
775 """Draw an arc."""
776 self._metafiles[self._currentAngle].DrawArc(centrePt, startPt, endPt)
777
778 def DrawEllipticArc(self, rect, startAngle, endAngle):
779 """Draw an elliptic arc."""
780 self._metafiles[self._currentAngle].DrawEllipticArc(rect, startAngle, endAngle)
781
782 def DrawPoint(self, pt):
783 self._metafiles[self._currentAngle].DrawPoint(pt)
784
785 def DrawText(self, text, pt):
786 self._metafiles[self._currentAngle].DrawText(text, pt)
787
788 def DrawLines(self, pts):
789 self._metafiles[self._currentAngle].DrawLines(pts)
790
791 def DrawPolygon(self, pts, flags = 0):
792 """Draw a polygon.
793
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).
797 """
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)
803
804 def DrawSpline(self, pts):
805 self._metafiles[self._currentAngle].DrawSpline(pts)
806
807 def SetClippingRect(self, rect):
808 """Set the clipping rectangle."""
809 self._metafiles[self._currentAngle].SetClippingRect(rect)
810
811 def DestroyClippingRect(self):
812 """Destroy the clipping rectangle."""
813 self._metafiles[self._currentAngle].DestroyClippingRect()
814
815 def SetDrawnPen(self, pen, isOutline = False):
816 """Set the pen for this metafile.
817
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).
821 """
822 self._metafiles[self._currentAngle].SetPen(pen, isOutline)
823
824 def SetDrawnBrush(self, brush, isFill = False):
825 """Set the brush for this metafile.
826
827 If isFill is True, the brush is used as the fill brush.
828 """
829 self._metafiles[self._currentAngle].SetBrush(brush, isFill)
830
831 def SetDrawnFont(self, font):
832 self._metafiles[self._currentAngle].SetFont(font)
833
834 def SetDrawnTextColour(self, colour):
835 """Set the current text colour for the current metafile."""
836 self._metafiles[self._currentAngle].SetTextColour(colour)
837
838 def SetDrawnBackgroundColour(self, colour):
839 """Set the current background colour for the current metafile."""
840 self._metafiles[self._currentAngle].SetBackgroundColour(colour)
841
842 def SetDrawnBackgroundMode(self, mode):
843 """Set the current background mode for the current metafile."""
844 self._metafiles[self._currentAngle].SetBackgroundMode(mode)
845
846 def CalculateSize(self):
847 """Calculate the wxDrawnShape size from the current metafile.
848
849 Call this after you have drawn into the shape.
850 """
851 self._metafiles[self._currentAngle].CalculateSize(self)
852
853 def DrawAtAngle(self, angle):
854 """Set the metafile for the given orientation, which can be one of:
855
856 * DRAWN_ANGLE_0
857 * DRAWN_ANGLE_90
858 * DRAWN_ANGLE_180
859 * DRAWN_ANGLE_270
860 """
861 self._currentAngle = angle
862
863 def GetAngle(self):
864 """Return the current orientation, which can be one of:
865
866 * DRAWN_ANGLE_0
867 * DRAWN_ANGLE_90
868 * DRAWN_ANGLE_180
869 * DRAWN_ANGLE_270
870 """
871 return self._currentAngle
872
873 def GetRotation(self):
874 """Return the current rotation of the shape in radians."""
875 return self._rotation
876
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.
883 """
884 self._saveToFile = save
885
886 def GetMetaFile(self, which = 0):
887 """Return a reference to the internal 'pseudo-metafile'."""
888 return self._metafiles[which]