]> git.saurik.com Git - wxWidgets.git/commitdiff
Adding DrawnShape (patch from Pierre Hjälm)
authorRobin Dunn <robin@alldunn.com>
Tue, 31 Aug 2004 14:54:13 +0000 (14:54 +0000)
committerRobin Dunn <robin@alldunn.com>
Tue, 31 Aug 2004 14:54:13 +0000 (14:54 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@28976 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

wxPython/demo/OGL.py
wxPython/wx/lib/ogl/__init__.py
wxPython/wx/lib/ogl/_basic.py
wxPython/wx/lib/ogl/_drawn.py [new file with mode: 0644]

index 0fad4004923a355cc898f378fa23663e0986e6c4..af0c047845b5dd7d763d6e958737c727c7ff71fb 100644 (file)
@@ -8,6 +8,10 @@
 # o Changed to use the python version of OGL
 # o Added TextShape, CompositeShape and CompositeShape with divisions
 #
+# 20040830 - Pierre Hjälm
+#
+# o Added DrawnShape
+#
 
 import wx
 import wx.lib.ogl as ogl
@@ -16,6 +20,38 @@ import  images
 
 #----------------------------------------------------------------------
 
+class DrawnShape(ogl.DrawnShape):
+    def __init__(self):
+        ogl.DrawnShape.__init__(self)
+
+        self.SetDrawnBrush(wx.WHITE_BRUSH)
+        self.SetDrawnPen(wx.BLACK_PEN)
+        self.DrawArc((0, -10), (30, 0), (-30, 0))
+
+        self.SetDrawnPen(wx.Pen("#ff8030"))
+        self.DrawLine((-30, 5), (30, 5))
+
+        self.SetDrawnPen(wx.Pen("#00ee10"))
+        self.DrawRoundedRectangle((-20, 10, 40, 10), 5)
+
+        self.SetDrawnPen(wx.Pen("#9090f0"))
+        self.DrawEllipse((-30, 25, 60, 20))
+
+        self.SetDrawnTextColour(wx.BLACK)
+        self.SetDrawnFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
+        self.DrawText("DrawText", (-26, 28))
+
+        self.SetDrawnBrush(wx.GREEN_BRUSH)
+        self.DrawPolygon([(-100, 5), (-45, 30), (-35, 20), (-30, 5)])
+
+        self.SetDrawnPen(wx.BLACK_PEN)
+        self.DrawLines([(30, -45), (40, -45), (40 ,45), (30, 45)])
+
+        # Make sure to call CalculateSize when all drawing is done
+        self.CalculateSize()
+        
+#----------------------------------------------------------------------
+
 class DiamondShape(ogl.PolygonShape):
     def __init__(self, w=0.0, h=0.0):
         ogl.PolygonShape.__init__(self)
@@ -247,7 +283,7 @@ class TestWindow(ogl.ShapeCanvas):
 
         self.MyAddShape(
             CompositeDivisionShape(self), 
-            310, 310, wx.BLACK_PEN, wx.BLUE_BRUSH, "Division"
+            270, 310, wx.BLACK_PEN, wx.BLUE_BRUSH, "Division"
             )
         
         self.MyAddShape(
@@ -270,14 +306,19 @@ class TestWindow(ogl.ShapeCanvas):
             305, 60, wx.BLACK_PEN, wx.LIGHT_GREY_BRUSH, "Rectangle"
             )
 
+        self.MyAddShape(
+            DrawnShape(),
+            500, 80, wx.BLACK_PEN, wx.BLACK_BRUSH, "DrawnShape"
+            )
+
         ds = self.MyAddShape(
             DividedShape(140, 150, self), 
-            515, 145, wx.BLACK_PEN, dsBrush, ''
+            520, 265, wx.BLACK_PEN, dsBrush, ''
             )
 
         self.MyAddShape(
             DiamondShape(90, 90), 
-            445, 305, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon"
+            355, 260, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon"
             )
             
         self.MyAddShape(
index 42da15c237f6eecea924ebd648ee034a4353656d..4629b2c6c1ce3245604f651f42af49474c1c9f9a 100644 (file)
@@ -10,7 +10,7 @@ from _lines import *
 from _bmpshape import *
 from _divided import *
 from _composit import *
-
+from _drawn import *
 
 
 # Set things up for documenting with epydoc.  The __docfilter__ will
index e0d5c7307fa5915ea91a1904233b844f55d22120..de7b94a2b08cd98b0208eb26fab46a20a7c226f1 100644 (file)
@@ -2881,7 +2881,7 @@ class EllipseShape(Shape):
                 y = top
                 # We now have the point on the bounding box: but get the point
                 # on the ellipse by imagining a vertical line from
-                # (x, self._ypos - self_height - 500) to (x, self._ypos) intersecting
+                # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting
                 # the ellipse.
 
                 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
diff --git a/wxPython/wx/lib/ogl/_drawn.py b/wxPython/wx/lib/ogl/_drawn.py
new file mode 100644 (file)
index 0000000..550137f
--- /dev/null
@@ -0,0 +1,886 @@
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name:         drawn.py
+# Purpose:      DrawnShape class
+#
+# Author:       Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created:      2004-08-25
+# RCS-ID:       $Id$
+# Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# License:      wxWindows license
+#----------------------------------------------------------------------------
+
+import os.path
+
+from _basic import RectangleShape
+from _oglmisc import *
+
+METAFLAGS_OUTLINE         = 1
+METAFLAGS_ATTACHMENTS     = 2
+
+DRAWN_ANGLE_0        = 0
+DRAWN_ANGLE_90       = 1
+DRAWN_ANGLE_180      = 2
+DRAWN_ANGLE_270      = 3
+
+# Drawing operations
+DRAWOP_SET_PEN               = 1
+DRAWOP_SET_BRUSH             = 2
+DRAWOP_SET_FONT              = 3
+DRAWOP_SET_TEXT_COLOUR       = 4
+DRAWOP_SET_BK_COLOUR         = 5
+DRAWOP_SET_BK_MODE           = 6
+DRAWOP_SET_CLIPPING_RECT     = 7
+DRAWOP_DESTROY_CLIPPING_RECT = 8
+
+DRAWOP_DRAW_LINE             = 20
+DRAWOP_DRAW_POLYLINE         = 21
+DRAWOP_DRAW_POLYGON          = 22
+DRAWOP_DRAW_RECT             = 23
+DRAWOP_DRAW_ROUNDED_RECT     = 24
+DRAWOP_DRAW_ELLIPSE          = 25
+DRAWOP_DRAW_POINT            = 26
+DRAWOP_DRAW_ARC              = 27
+DRAWOP_DRAW_TEXT             = 28
+DRAWOP_DRAW_SPLINE           = 29
+DRAWOP_DRAW_ELLIPTIC_ARC     = 30
+
+class DrawOp(object):
+    def __init__(self, theOp):
+        self._op = theOp
+
+    def GetOp(self):
+        return self._op
+
+    def GetPerimeterPoint(self, x1, y1, x2, y2, xOffset, yOffset, attachmentMode):
+        return False
+
+    def Scale(self,scaleX, scaleY):
+        pass
+
+    def Translate(self, x, y):
+        pass
+    
+class OpSetGDI(DrawOp):
+    """Set font, brush, text colour."""
+    def __init__(self, theOp, theImage, theGdiIndex, theMode = 0):
+        DrawOp.__init__(self, theOp)
+
+        self._gdiIndex = theGdiIndex
+        self._image = theImage
+        self._mode = theMode
+
+    def Do(self, dc, xoffset = 0, yoffset = 0):
+        if self._op == DRAWOP_SET_PEN:
+            # Check for overriding this operation for outline colour
+            if self._gdiIndex in self._image._outlineColours:
+                if self._image._outlinePen:
+                    dc.SetPen(self._image._outlinePen)
+            else:
+                try:
+                    dc.SetPen(self._image._gdiObjects[self._gdiIndex])
+                except IndexError:
+                    pass
+        elif self._op == DRAWOP_SET_BRUSH:
+            # Check for overriding this operation for outline or fill colour
+            if self._gdiIndex in self._image._outlineColours:
+                # Need to construct a brush to match the outline pen's colour
+                if self._image._outlinePen:
+                    br = wx.TheBrushList.FindOrCreateBrush(self._image._outlinePen, wx.SOLID)
+                    if br:
+                        dc.SetBrush(br)
+            elif self._gdiIndex in self._image._fillColours:
+                if self._image._fillBrush:
+                    dc.SetBrush(self._image._fillBrush)
+            else:
+                brush = self._image._gdiObjects[self._gdiIndex]
+                if brush:
+                    dc.SetBrush(brush)
+        elif self._op == DRAWOP_SET_FONT:
+            try:
+                dc.SetFont(self._image._gdiObjects[self._gdiIndex])
+            except IndexError:
+                pass
+        elif self._op == DRAWOP_SET_TEXT_COLOUR:
+            dc.SetTextForeground(wx.Colour(self._r, self._g, self._b))
+        elif self._op == DRAWOP_SET_BK_COLOUR:
+            dc.SetTextBackground(wx.Colour(self._r, self._g, self._b))
+        elif self._op == DRAWOP_SET_BK_MODE:
+            dc.SetBackgroundMode(self._mode)
+
+    
+class OpSetClipping(DrawOp):
+    """Set/destroy clipping."""
+    def __init__(self, theOp, theX1, theY1, theX2, theY2):
+        DrawOp.__init__(self, theOp)
+
+        self._x1 = theX1
+        self._y1 = theY1
+        self._x2 = theX2
+        self._y2 = theY2
+
+    def Do(self, dc, xoffset, yoffset):
+        if self._op == DRAWOP_SET_CLIPPING_RECT:
+            dc.SetClippingRegion(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
+        elif self._op == DRAWOP_DESTROY_CLIPPING_RECT:
+            dc.DestroyClippingRegion()
+
+    def Scale(self, scaleX, scaleY):
+        self._x1 *= scaleX
+        self._y1 *= scaleY
+        self._x2 *= scaleX
+        self._y2 *= scaleY
+
+    def Translate(self, x, y):
+        self._x1 += x
+        self._y1 += y
+
+
+class OpDraw(DrawOp):
+    """Draw line, rectangle, rounded rectangle, ellipse, point, arc, text."""
+    def __init__(self, theOp, theX1, theY1, theX2, theY2, theRadius = 0.0, s = ""):
+        DrawOp.__init__(self, theOp)
+
+        self._x1 = theX1
+        self._y1 = theY1
+        self._x2 = theX2
+        self._y2 = theY2
+        self._x3 = 0.0
+        self._y3 = 0.0
+        self._radius = theRadius
+        self._textString = s
+
+    def Do(self, dc, xoffset, yoffset):
+        if self._op == DRAWOP_DRAW_LINE:
+            dc.DrawLine(self._x1 + xoffset, self._y1 + yoffset, self._x2 + xoffset, self._y2 + yoffset)
+        elif self._op == DRAWOP_DRAW_RECT:
+            dc.DrawRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
+        elif self._op == DRAWOP_DRAW_ROUNDED_RECT:
+            dc.DrawRoundedRectangle(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._radius)
+        elif self._op == DRAWOP_DRAW_ELLIPSE:
+            dc.DrawEllipse(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2)
+        elif self._op == DRAWOP_DRAW_ARC:
+            dc.DrawArc(self._x2 + xoffset, self._y2 + yoffset, self._x3 + xoffset, self._y3 + yoffset, self._x1 + xoffset, self._y1 + yoffset)
+        elif self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
+            dc.DrawEllipticArc(self._x1 + xoffset, self._y1 + yoffset, self._x2, self._y2, self._x3 * 360 / (2 * math.pi), self.y3 * 360 / (2 * math.pi))
+        elif self._op == DRAWOP_DRAW_POINT:
+            dc.DrawPoint(self._x1 + xoffset, self._y1 + yoffset)
+        elif self._op == DRAWOP_DRAW_TEXT:
+            dc.DrawText(self._textString, self._x1 + xoffset, self._y1 + yoffset)
+    def Scale(self, scaleX, scaleY):
+        self._x1 *= scaleX
+        self._y1 *= scaleY
+        self._x2 *= scaleX
+        self._y2 *= scaleY
+
+        if self._op != DRAWOP_DRAW_ELLIPTIC_ARC:
+            self._x3 *= scaleX
+            self._y3 *= scaleY
+
+        self._radius *= scaleX
+        
+    def Translate(self, x, y):
+        self._x1 += x
+        self._y1 += y
+
+        if self._op == DRAWOP_DRAW_LINE:
+            self._x2 += x
+            self._y2 += y
+        elif self._op == DRAWOP_DRAW_ARC:
+            self._x2 += x
+            self._y2 += y
+            self._x3 += x
+            self._y3 += y
+
+    def Rotate(self, x, y, theta, sinTheta, cosTheta):
+        newX1 = self._x1 * cosTheta + self._y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+        newY1 = self._x1 * sinTheta + self._y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+        if self._op == DRAWOP_DRAW_LINE:
+            newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+            newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta;
+
+            self._x1 = newX1
+            self._y1 = newY1
+            self._x2 = newX2
+            self._y2 = newY2
+
+        elif self._op in [DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPTIC_ARC]:
+            # Assume only 0, 90, 180, 270 degree rotations.
+            # oldX1, oldY1 represents the top left corner. Find the
+            # bottom right, and rotate that. Then the width/height is
+            # the difference between x/y values.
+            oldBottomRightX = self._x1 + self._x2
+            oldBottomRightY = self._y1 + self._y2
+            newBottomRightX = oldBottomRightX * cosTheta - oldBottomRightY * sinTheta + x * (1 - cosTheta) + y * sinTheta
+            newBottomRightY = oldBottomRightX * sinTheta + oldBottomRightY * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+            # Now find the new top-left, bottom-right coordinates.
+            minX = min(newX1, newBottomRightX)
+            minY = min(newY1, newBottomRightY)
+            maxX = max(newX1, newBottomRightX)
+            maxY = max(newY1, newBottomRightY)
+
+            self._x1 = minX
+            self._y1 = minY
+            self._x2 = maxX - minX # width
+            self._y2 = maxY - minY # height
+
+            if self._op == DRAWOP_DRAW_ELLIPTIC_ARC:
+                # Add rotation to angles
+                self._x3 += theta
+                self._y3 += theta
+        elif self._op == DRAWOP_DRAW_ARC:
+            newX2 = self._x2 * cosTheta - self._y2 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+            newY2 = self._x2 * sinTheta + self._y2 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+            newX3 = self._x3 * cosTheta - self._y3 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+            newY3 = self._x3 * sinTheta + self._y3 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+            self._x1 = newX1
+            self._y1 = newY1
+            self._x2 = newX2
+            self._y2 = newY2
+            self._x3 = newX3
+            self._y3 = newY3
+
+
+class OpPolyDraw(DrawOp):
+    """Draw polygon, polyline, spline."""
+    def __init__(self, theOp, thePoints):
+        DrawOp.__init__(self, theOp)
+
+        self._noPoints = len(thePoints)
+        self._points = thePoints
+
+    def Do(self, dc, xoffset, yoffset):
+        if self._op == DRAWOP_DRAW_POLYLINE:
+            dc.DrawLines(self._points, xoffset, yoffset)
+        elif self._op == DRAWOP_DRAW_POLYGON:
+            dc.DrawPolygon(self._points, xoffset, yoffset)
+        elif self._op == DRAWOP_DRAW_SPLINE:
+            dc.DrawSpline(self._points) # no offsets in DrawSpline
+
+    def Scale(self, scaleX, scaleY):
+        for i in range(self._noPoints):
+            self._points[i] = self._points[i][0] * scaleX, self._points[i][1] * scaleY
+
+    def Translate(self, x, y):
+        for i in range(self._noPoints):
+            self._points[i][0] += x
+            self._points[i][1] += y
+
+    def Rotate(self, x, y, theta, sinTheta, cosTheta):
+        for i in range(self._noPoints):
+            x1 = self._points[i][0]
+            y1 = self._points[i][1]
+
+            self._points[i][0] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+            self._points[i][1] = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+    def OnDrawOutline(self, dc, x, y, w, h, oldW, oldH):
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+        # Multiply all points by proportion of new size to old size
+        x_proportion = abs(w / oldW)
+        y_proportion = abs(h / oldH)
+
+        dc.DrawPolygon([(x_proportion * x, y_proportion * y) for x, y in self._points], x, y)
+
+    def GetPerimeterPoint(self, x1, y1, x2, y2, xOffset, yOffset, attachmentMode):
+        # First check for situation where the line is vertical,
+        # and we would want to connect to a point on that vertical --
+        # oglFindEndForPolyline can't cope with this (the arrow
+        # gets drawn to the wrong place).
+        if attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
+            # Look for the point we'd be connecting to. This is
+            # a heuristic...
+            for point in self._points:
+                if point[0] == 0:
+                    if y2 > y1 and point[1] > 0:
+                        return point[0]+xOffset, point[1]+yOffset
+                    elif y2 < y1 and point[1] < 0:
+                        return point[0]+xOffset, point[1]+yOffset
+
+        return FindEndForPolyline([ p[0] + xOffset for p in self._points ],
+                                  [ p[1] + yOffset for p in self._points ],
+                                  x1, y1, x2, y2)
+    
+                    
+class PseudoMetaFile(object):
+    """
+    A simple metafile-like class which can load data from a Windows
+    metafile on all platforms.
+    """
+    def __init__(self):
+        self._currentRotation = 0
+        self._rotateable = True
+        self._width = 0.0
+        self._height = 0.0
+        self._outlinePen = None
+        self._fillBrush = None
+        self._outlineOp = -1
+        
+        self._ops = []
+        self._gdiObjects = []
+        
+        self._outlineColours = []
+        self._fillColours = []
+
+    def Clear(self):
+        self._ops = []
+        self._gdiObjects = []
+        self._outlineColours = []
+        self._fillColours = []
+        self._outlineColours = -1
+
+    def IsValid(self):
+        return self._ops != []
+
+    def GetOps(self):
+        return self._ops
+
+    def SetOutlineOp(self, op):
+        self._outlineOp = op
+
+    def GetOutlineOp(self):
+        return self._outlineOp
+
+    def SetOutlinePen(self, pen):
+        self._outlinePen = pen
+
+    def GetOutlinePen(self, pen):
+        return self._outlinePen
+
+    def SetFillBrush(self, brush):
+        self._fillBrush = brush
+
+    def GetFillBrush(self):
+        return self._fillBrush
+
+    def SetSize(self, w, h):
+        self._width = w
+        self._height = h
+
+    def SetRotateable(self, rot):
+        self._rotateable = rot
+        
+    def GetRotateable(self):
+        return self._rotateable
+
+    def GetFillColours(self):
+        return self._fillColours
+
+    def GetOutlineColours(self):
+        return self._outlineColours
+    
+    def Draw(self, dc, xoffset, yoffset):
+        for op in self._ops:
+            op.Do(dc, xoffset, yoffset)
+
+    def Scale(self, sx, sy):
+        for op in self._ops:
+            op.Scale(sx, sy)
+
+        self._width *= sx
+        self._height *= sy
+
+    def Translate(self, x, y):
+        for op in self._ops:
+            op.Translate(x, y)
+
+    def Rotate(self, x, y, theta):
+        theta1 = theta - self._currentRotation
+        if theta1 == 0:
+            return
+
+        cosTheta = cos(theta1)
+        sinTheta = sin(theta1)
+
+        for op in self._ops:
+            op.Rotate(x, y, theta, sinTheta, cosTheta)
+
+        self._currentRotation = theta
+
+    def LoadFromMetaFile(self, filename, rwidth, rheight):
+        if not os.path.exist(filename):
+            return False
+
+        print "LoadFromMetaFile not implemented yet."
+        return False # TODO
+
+    # Scale to fit size
+    def ScaleTo(self, w, h):
+        scaleX = w / self._width
+        scaleY = h / self._height
+
+        self.Scale(scaleX, scaleY)
+
+    def GetBounds(self):
+        maxX, maxY, minX, minY = -99999.9, -99999.9, 99999.9, 99999.9
+
+        for op in self._ops:
+            if op.GetOp() in [DRAWOP_DRAW_LINE, DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE, DRAWOP_DRAW_POINT, DRAWOP_DRAW_TEXT]:
+                if op._x1 < minX:
+                    minX = op._x1
+                if op._x1 > maxX:
+                    maxX = op._x1
+                if op._y1 < minY:
+                    minY = op._y1
+                if op._y1 > maxY:
+                    maxY = op._y1
+                if op.GetOp() == DRAWOP_DRAW_LINE:
+                    if op._x2 < minX:
+                        minX = op._x2
+                    if op._x2 > maxX:
+                        maxX = op._x2
+                    if op._y2 < minY:
+                        minY = op._y2
+                    if op._y2 > maxY:
+                        maxY = op._y2
+                elif op.GetOp() in [ DRAWOP_DRAW_RECT, DRAWOP_DRAW_ROUNDED_RECT, DRAWOP_DRAW_ELLIPSE]:
+                    if op._x1 + op._x2 < minX:
+                        minX = op._x1 + op._x2
+                    if op._x1 + op._x2 > maxX:
+                        maxX = op._x1 + op._x2
+                    if op._y1 + op._y2 < minY:
+                        minY = op._y1 + op._y2
+                    if op._y1 + op._y2 > maxX:
+                        maxY = op._y1 + op._y2
+            elif op.GetOp() == DRAWOP_DRAW_ARC:
+                # TODO: don't yet know how to calculate the bounding box
+                # for an arc. So pretend it's a line; to get a correct
+                # bounding box, draw a blank rectangle first, of the 
+                # correct size.
+                if op._x1 < minX:
+                    minX = op._x1
+                if op._x1 > maxX:
+                    maxX = op._x1
+                if op._y1 < minY:
+                    minY = op._y1
+                if op._y1 > maxY:
+                    maxY = op._y1
+                if op._x2 < minX:
+                    minX = op._x2
+                if op._x2 > maxX:
+                    maxX = op._x2
+                if op._y2 < minY:
+                    minY = op._y2
+                if op._y2 > maxY:
+                    maxY = op._y2
+            elif op.GetOp() in [DRAWOP_DRAW_POLYLINE, DRAWOP_DRAW_POLYGON, DRAWOP_DRAW_SPLINE]:
+                for point in op._points:
+                    if point[0] < minX:
+                        minX = point[0]
+                    if point[0] > maxX:
+                        maxX = point[0]
+                    if point[1] < minY:
+                        minY = point[1]
+                    if point[1] > maxY:
+                        maxY = point[1]
+
+        return [minX, minY, maxX, maxY]
+
+    # Calculate size from current operations
+    def CalculateSize(self, shape):
+        boundMinX, boundMinY, boundMaxX, boundMaxY = self.GetBounds()
+
+        # By Pierre Hjälm: This is NOT in the old version, which
+        # gets this totally wrong. Since the drawing is centered, we
+        # cannot get the width by measuring from left to right, we
+        # must instead make enough room to handle the largest
+        # coordinates
+        #self.SetSize(boundMaxX - boundMinX, boundMaxY - boundMinY)
+
+        w = max(abs(boundMinX), abs(boundMaxX)) * 2
+        h = max(abs(boundMinY), abs(boundMaxY)) * 2
+        
+        self.SetSize(w, h)
+        
+        if shape:
+            shape.SetWidth(self._width)
+            shape.SetHeight(self._height)
+
+    # Set of functions for drawing into a pseudo metafile
+    def DrawLine(self, pt1, pt2):
+        op = OpDraw(DRAWOP_DRAW_LINE, pt1[0], pt1[1], pt2[0], pt2[1])
+        self._ops.append(op)
+
+    def DrawRectangle(self, rect):
+        op = OpDraw(DRAWOP_DRAW_RECT, rect[0], rect[1], rect[2], rect[3])
+        self._ops.append(op)
+
+    def DrawRoundedRectangle(self, rect, radius):
+        op = OpDraw(DRAWOP_DRAW_ROUNDED_RECT, rect[0], rect[1], rect[2], rect[3])
+        op._radius = radius
+        self._ops.append(op)
+
+    def DrawEllipse(self, rect):
+        op = OpDraw(DRAWOP_DRAW_ELLIPSE, rect[0], rect[1], rect[2], rect[3])
+        self._ops.append(op)
+
+    def DrawArc(self, centrePt, startPt, endPt):
+        op = OpDraw(DRAWOP_DRAW_ARC, centrePt[0], centrePt[1], startPt[0], startPt[1])
+        op._x3, op._y3 = endPt
+
+        self._ops.append(op)
+
+    def DrawEllipticArc(self, rect, startAngle, endAngle):
+        startAngleRadians = startAngle * math.pi * 2 / 360
+        endAngleRadians = endAngle * math.pi * 2 / 360
+
+        op = OpDraw(DRAWOP_DRAW_ELLIPTIC_ARC, rect[0], rect[1], rect[2], rect[3])
+        op._x3 = startAngleRadians
+        op._y3 = endAngleRadians
+
+        self._ops.append(op)
+
+    def DrawPoint(self, pt):
+        op = OpDraw(DRAWOP_DRAW_POINT, pt[0], pt[1], 0, 0)
+        self._ops.append(op)
+
+    def DrawText(self, text, pt):
+        op = OpDraw(DRAWOP_DRAW_TEXT, pt[0], pt[1], 0, 0)
+        op._textString = text
+        self._ops.append(op)
+
+    def DrawLines(self, pts):
+        op = OpPolyDraw(DRAWOP_DRAW_POLYLINE, pts)
+        self._ops.append(op)
+
+    # flags:
+    # oglMETAFLAGS_OUTLINE: will be used for drawing the outline and
+    #                       also drawing lines/arrows at the circumference.
+    # oglMETAFLAGS_ATTACHMENTS: will be used for initialising attachment
+    #                       points at the vertices (perhaps a rare case...)
+    def DrawPolygon(self, pts, flags = 0):
+        op = OpPolyDraw(DRAWOP_DRAW_POLYGON, pts)
+        self._ops.append(op)
+
+        if flags & METAFLAGS_OUTLINE:
+            self._outlineOp = len(self._ops) - 1
+
+    def DrawSpline(self, pts):
+        op = OpPolyDraw(DRAWOP_DRAW_SPLINE, pts)
+        self._ops.append(op)
+
+    def SetClippingRect(self, rect):
+        OpSetClipping(DRAWOP_SET_CLIPPING_RECT, rect[0], rect[1], rect[2], rect[3])
+
+    def DestroyClippingRect(self):
+        op = OpSetClipping(DRAWOP_DESTROY_CLIPPING_RECT, 0, 0, 0, 0)
+        self._ops.append(op)
+
+    def SetPen(self, pen, isOutline = False):
+        self._gdiObjects.append(pen)
+        op = OpSetGDI(DRAWOP_SET_PEN, self, len(self._gdiObjects) - 1)
+        self._ops.append(op)
+
+        if isOutline:
+            self._outlineColours.append(len(self._gdiObjects) - 1)
+
+    def SetBrush(self, brush, isFill = False):
+        self._gdiObjects.append(brush)
+        op = OpSetGDI(DRAWOP_SET_BRUSH, self, len(self._gdiObjects) - 1)
+        self._ops.append(op)
+
+        if isFill:
+            self._fillColours.append(len(self._gdiObjects) - 1)
+
+    def SetFont(self, font):
+        self._gdiObjects.append(font)
+        op = OpSetGDI(DRAWOP_SET_FONT, self, len(self._gdiObjects) - 1)
+        self._ops.append(op)
+
+    def SetTextColour(self, colour):
+        op = OpSetGDI(DRAWOP_SET_TEXT_COLOUR, self, 0)
+        op._r, op._g, op._b = colour.Red(), colour.Green(), colour.Blue()
+
+        self._ops.append(op)
+
+    def SetBackgroundColour(self, colour):
+        op = OpSetGDI(DRAWOP_SET_BK_COLOUR, self, 0)
+        op._r, op._g, op._b = colour.Red(), colour.Green(), colour.Blue()
+
+        self._ops.append(op)
+
+    def SetBackgroundMode(self, mode):
+        op = OpSetGDI(DRAWOP_SET_BK_MODE, self, 0)
+        self._ops.append(op)
+        
+class DrawnShape(RectangleShape):
+    """
+    Draws a pseudo-metafile shape, which can be loaded from a simple
+    Windows metafile.
+
+    wxDrawnShape allows you to specify a different shape for each of four
+    orientations (North, West, South and East). It also provides a set of
+    drawing functions for programmatic drawing of a shape, so that during
+    construction of the shape you can draw into it as if it were a device
+    context.
+
+    Derived from:
+      RectangleShape
+    """
+    def __init__(self):
+        RectangleShape.__init__(self, 100, 50)
+        self._saveToFile = True
+        self._currentAngle = DRAWN_ANGLE_0
+        
+        self._metafiles=PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile(), PseudoMetaFile()
+
+    def OnDraw(self, dc):
+        # Pass pen and brush in case we have force outline
+        # and fill colours
+        if self._shadowMode != SHADOW_NONE:
+            if self._shadowBrush:
+                self._metafiles[self._currentAngle]._fillBrush = self._shadowBrush
+            self._metafiles[self._currentAngle]._outlinePen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)
+            self._metafiles[self._currentAngle].Draw(dc, self._xpos + self._shadowOffsetX, self._ypos + self._shadowOffsetY)
+
+        self._metafiles[self._currentAngle]._outlinePen = self._pen
+        self._metafiles[self._currentAngle]._fillBrush = self._brush
+        self._metafiles[self._currentAngle].Draw(dc, self._xpos, self._ypos)
+
+    def SetSize(self, w, h, recursive = True):
+        self.SetAttachmentSize(w, h)
+
+        if self.GetWidth() == 0.0:
+            scaleX = 1
+        else:
+            scaleX = w / self.GetWidth()
+
+        if self.GetHeight() == 0.0:
+            scaleY = 1
+        else:
+            scaleY = h / self.GetHeight()
+
+        for i in range(4):
+            if self._metafiles[i].IsValid():
+                self._metafiles[i].Scale(scaleX, scaleY)
+
+        self._width = w
+        self._height = h
+        self.SetDefaultRegionSize()
+
+    def Scale(self, sx, sy):
+        """Scale the shape by the given amount."""
+        for i in range(4):
+            if self._metafiles[i].IsValid():
+                self._metafiles[i].Scale(sx, sy)
+                self._metafiles[i].CalculateSize(self)
+
+    def Translate(self, x, y):
+        """Translate the shape by the given amount."""
+        for i in range(4):
+            if self._metafiles[i].IsValid():
+                self._metafiles[i].Translate(x, y)
+                self._metafiles[i].CalculateSize(self)
+
+    # theta is absolute rotation from the zero position
+    def Rotate(self, x, y, theta):
+        """Rotate about the given axis by the given amount in radians."""
+        self._currentAngle = self.DetermineMetaFile(theta)
+
+        if self._currentAngle == 0:
+            # Rotate metafile
+            if not self._metafiles[0].GetRotateable():
+                return
+
+            self._metafiles[0].Rotate(x, y, theta)
+
+        actualTheta = theta - self._rotation
+
+        # Rotate attachment points
+        sinTheta = math.sin(actualTheta)
+        cosTheta = math.cos(actualTheta)
+
+        for point in self._attachmentPoints:
+            x1 = point._x
+            y1 = point._y
+
+            point._x = x1 * cosTheta - y1 * sinTheta + x * (1.0 - cosTheta) + y * sinTheta
+            point._y = x1 * sinTheta + y1 * cosTheta + y * (1.0 - cosTheta) + x * sinTheta
+
+        self._rotation = theta
+
+        self._metafiles[self._currentAngle].CalculateSize(self)
+
+    # Which metafile do we use now? Based on current rotation and validity
+    # of metafiles.
+    def DetermineMetaFile(self, rotation):
+        tolerance = 0.0001
+        angles = [0.0, math.pi / 2, math.pi, 3 * pi / 2]
+
+        whichMetaFile = 0
+
+        for i in range(4):
+            if RoughlyEqual(rotation, angles[i], tolerance):
+                whichMetaFile = i
+                break
+
+        if whichMetaFile > 0 and not self._metafiles[whichMetaFile].IsValid():
+            whichMetaFile = 0
+
+        return whichMetaFile
+
+    def OnDrawOutline(self, dc, x, y, w, h):
+        if self._metafiles[self._currentAngle].GetOutlineOp() != -1:
+            op = self._metafiles[self._currentAngle].GetOps()[self._metafiles[self._currentAngle].GetOutlineOp()]
+            if op.OnDrawOutline(dc, x, y, w, h, self._width, self._height):
+                return
+
+        # Default... just use a rectangle
+        RectangleShape.OnDrawOutline(self, dc, x, y, w, h)
+
+    # Get the perimeter point using the special outline op, if there is one,
+    # otherwise use default wxRectangleShape scheme
+    def GetPerimeterPoint(self, x1, y1, x2, y2):
+        if self._metafiles[self._currentAngle].GetOutlineOp() != -1:
+            op = self._metafiles[self._currentAngle].GetOps()[self._metafiles[self._currentAngle].GetOutlineOp()]
+            p = op.GetPerimeterPoint(x1, y1, x2, y2, self.GetX(), self.GetY(), self.GetAttachmentMode())
+            if p:
+                return p
+            
+        return RectangleShape.GetPerimeterPoint(self, x1, y1, x2, y2)
+
+    def LoadFromMetaFile(self, filename):
+        """Load a (very simple) Windows metafile, created for example by
+        Top Draw, the Windows shareware graphics package."""
+        return self._metafiles[0].LoadFromMetaFile(filename)
+
+    # Set of functions for drawing into a pseudo metafile.
+    # They use integers, but doubles are used internally for accuracy
+    # when scaling.
+    def DrawLine(self, pt1, pt2):
+        self._metafiles[self._currentAngle].DrawLine(pt1, pt2)
+
+    def DrawRectangle(self, rect):
+        self._metafiles[self._currentAngle].DrawRectangle(rect)
+
+    def DrawRoundedRectangle(self, rect, radius):
+        """Draw a rounded rectangle.
+
+        radius is the corner radius. If radius is negative, it expresses
+        the radius as a proportion of the smallest dimension of the rectangle.
+        """
+        self._metafiles[self._currentAngle].DrawRoundedRectangle(rect, radius)
+
+    def DrawEllipse(self, rect):
+        self._metafiles[self._currentAngle].DrawEllipse(rect)
+
+    def DrawArc(self, centrePt, startPt, endPt):
+        """Draw an arc."""
+        self._metafiles[self._currentAngle].DrawArc(centrePt, startPt, endPt)
+
+    def DrawEllipticArc(self, rect, startAngle, endAngle):
+        """Draw an elliptic arc."""
+        self._metafiles[self._currentAngle].DrawEllipticArc(rect, startAngle, endAngle)
+
+    def DrawPoint(self, pt):
+        self._metafiles[self._currentAngle].DrawPoint(pt)
+
+    def DrawText(self, text, pt):
+        self._metafiles[self._currentAngle].DrawText(text, pt)
+
+    def DrawLines(self, pts):
+        self._metafiles[self._currentAngle].DrawLines(pts)
+
+    def DrawPolygon(self, pts, flags = 0):
+        """Draw a polygon.
+
+        flags can be one or more of:
+        METAFLAGS_OUTLINE (use this polygon for the drag outline) and
+        METAFLAGS_ATTACHMENTS (use the vertices of this polygon for attachments).
+        """
+        if flags and METAFLAGS_ATTACHMENTS:
+            self.ClearAttachments()
+            for i in range(len(pts)):
+                self._attachmentPoints.append(AttachmentPoint(i,pts[i][0],pts[i][1]))
+        self._metafiles[self._currentAngle].DrawPolygon(pts, flags)
+
+    def DrawSpline(self, pts):
+        self._metafiles[self._currentAngle].DrawSpline(pts)
+
+    def SetClippingRect(self, rect):
+        """Set the clipping rectangle."""
+        self._metafiles[self._currentAngle].SetClippingRect(rect)
+
+    def DestroyClippingRect(self):
+        """Destroy the clipping rectangle."""
+        self._metafiles[self._currentAngle].DestroyClippingRect()
+
+    def SetDrawnPen(self, pen, isOutline = False):
+        """Set the pen for this metafile.
+
+        If isOutline is True, this pen is taken to indicate the outline
+        (and if the outline pen is changed for the whole shape, the pen
+        will be replaced with the outline pen).
+        """
+        self._metafiles[self._currentAngle].SetPen(pen, isOutline)
+
+    def SetDrawnBrush(self, brush, isFill = False):
+        """Set the brush for this metafile.
+
+        If isFill is True, the brush is used as the fill brush.
+        """
+        self._metafiles[self._currentAngle].SetBrush(brush, isFill)
+
+    def SetDrawnFont(self, font):
+        self._metafiles[self._currentAngle].SetFont(font)
+
+    def SetDrawnTextColour(self, colour):
+        """Set the current text colour for the current metafile."""
+        self._metafiles[self._currentAngle].SetTextColour(colour)
+
+    def SetDrawnBackgroundColour(self, colour):
+        """Set the current background colour for the current metafile."""
+        self._metafiles[self._currentAngle].SetBackgroundColour(colour)
+
+    def SetDrawnBackgroundMode(self, mode):
+        """Set the current background mode for the current metafile."""
+        self._metafiles[self._currentAngle].SetBackgroundMode(mode)
+
+    def CalculateSize(self):
+        """Calculate the wxDrawnShape size from the current metafile.
+
+        Call this after you have drawn into the shape.
+        """
+        self._metafiles[self._currentAngle].CalculateSize(self)
+
+    def DrawAtAngle(self, angle):
+        """Set the metafile for the given orientation, which can be one of:
+
+        * DRAWN_ANGLE_0
+        * DRAWN_ANGLE_90
+        * DRAWN_ANGLE_180
+        * DRAWN_ANGLE_270
+        """
+        self._currentAngle = angle
+
+    def GetAngle(self):
+        """Return the current orientation, which can be one of:
+
+        * DRAWN_ANGLE_0
+        * DRAWN_ANGLE_90
+        * DRAWN_ANGLE_180
+        * DRAWN_ANGLE_270
+        """
+        return self._currentAngle
+    
+    def GetRotation(self):
+        """Return the current rotation of the shape in radians."""
+        return self._rotation
+
+    def SetSaveToFile(self, save):
+        """If save is True, the image will be saved along with the shape's
+        other attributes. The reason why this might not be desirable is that
+        if there are many shapes with the same image, it would be more
+        efficient for the application to save one copy, and not duplicate
+        the information for every shape. The default is True.
+        """
+        self._saveToFile = save
+
+    def GetMetaFile(self, which = 0):
+        """Return a reference to the internal 'pseudo-metafile'."""
+        return self._metafiles[which]