From: Robin Dunn Date: Tue, 31 Aug 2004 14:54:13 +0000 (+0000) Subject: Adding DrawnShape (patch from Pierre Hjälm) X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/79609e6f24dbd9382a8351d10d436c6e115bda2e Adding DrawnShape (patch from Pierre Hjälm) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@28976 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/wxPython/demo/OGL.py b/wxPython/demo/OGL.py index 0fad400492..af0c047845 100644 --- a/wxPython/demo/OGL.py +++ b/wxPython/demo/OGL.py @@ -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( diff --git a/wxPython/wx/lib/ogl/__init__.py b/wxPython/wx/lib/ogl/__init__.py index 42da15c237..4629b2c6c1 100644 --- a/wxPython/wx/lib/ogl/__init__.py +++ b/wxPython/wx/lib/ogl/__init__.py @@ -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 diff --git a/wxPython/wx/lib/ogl/_basic.py b/wxPython/wx/lib/ogl/_basic.py index e0d5c7307f..de7b94a2b0 100644 --- a/wxPython/wx/lib/ogl/_basic.py +++ b/wxPython/wx/lib/ogl/_basic.py @@ -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 index 0000000000..550137f3ee --- /dev/null +++ b/wxPython/wx/lib/ogl/_drawn.py @@ -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]