diff --git a/etg/graphics.py b/etg/graphics.py index 9e510ba0..732c9c5c 100644 --- a/etg/graphics.py +++ b/etg/graphics.py @@ -1,6 +1,7 @@ #--------------------------------------------------------------------------- # Name: etg/graphics.py # Author: Kevin Ollivier +# Robin Dunn # # Created: 10-Sept-2011 # Copyright: (c) 2011 by Kevin Ollivier @@ -18,26 +19,28 @@ DOCSTRING = "" # The classes and/or the basename of the Doxygen XML files to be processed by # this script. ITEMS = [ + 'wxGraphicsObject', 'wxGraphicsBitmap', 'wxGraphicsBrush', - 'wxGraphicsContext', 'wxGraphicsFont', + 'wxGraphicsPen', + 'wxGraphicsContext', 'wxGraphicsGradientStop', 'wxGraphicsGradientStops', 'wxGraphicsMatrix', - 'wxGraphicsObject', 'wxGraphicsPath', - 'wxGraphicsPen', 'wxGraphicsRenderer', ] +OTHERDEPS = [ 'src/Point2D_helpers.cpp'] + #--------------------------------------------------------------------------- def run(): # Parse the XML file(s) building a collection of Extractor objects module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING) etgtools.parseDoxyXML(module, ITEMS) - + #----------------------------------------------------------------- # Tweak the parsed meta objects in the module object as needed for # customizing the generated code and docstrings. @@ -46,14 +49,31 @@ def run(): def markFactories(klass): for func in klass.allItems(): - if isinstance(func, etgtools.FunctionDef) and func.name.startswith('Create'): + if isinstance(func, etgtools.FunctionDef) \ + and func.name.startswith('Create') \ + and '*' in func.type: func.factory = True - + + #--------------------------------------------- + c = module.find('wxGraphicsObject') + assert isinstance(c, etgtools.ClassDef) + c.addCppMethod('bool', 'IsOk', '()', 'return !self->IsNull();') + c.addCppMethod('int', '__nonzero__', '()', "return !self->IsNull();") + + + #--------------------------------------------- c = module.find('wxGraphicsContext') assert isinstance(c, etgtools.ClassDef) markFactories(c) tools.removeVirtuals(c) c.abstract = True + + # ensure that the target DC lives as long as the GC does + for m in c.find('Create').all(): + for p in m.items: + if 'DC' in p.name or p.name == 'image': + p.keepReference = True + # FIXME: Handle wxEnhMetaFileDC? c.find('Create').findOverload('wxEnhMetaFileDC').ignore() @@ -61,16 +81,98 @@ def run(): # SIP doesn't like default parameter values to use dereference syntax, # (such as "col = *wxBLACK") so tweak the syntax a bit by using a macro. c.addHeaderCode("#define BLACK *wxBLACK") - for m in [c.find('CreateFont')] + c.find('CreateFont').overloads: + for m in c.find('CreateFont').all(): m.find('col').default = 'BLACK' + + m = c.find('GetPartialTextExtents') + m.find('widths').ignore() + m.type = 'wxArrayDouble*' + m.setCppCode("""\ + wxArrayDouble rval; + self->GetPartialTextExtents(*text, rval); + return new wxArrayDouble(rval); + """) + m = c.find('GetTextExtent') + m.pyName = 'GetFullTextExtent' + m.find('width').out = True + m.find('height').out = True + m.find('descent').out = True + m.find('externalLeading').out = True + m2 = c.addCppMethod('PyObject*', 'GetTextExtent', '(const wxString& text)', + pyArgsString="(text) -> (width, height)", + doc="Gets the dimensions of the string using the currently selected font.", + body="""\ + wxDouble width = 0.0, height = 0.0; + self->GetTextExtent(*text, &width, &height, NULL, NULL); + return sipBuildResult(0, "(dd)", width, height); + """) + c.items.remove(m2) + c.insertItemAfter(m, m2) + + c.addPyCode("GraphicsContext.DrawRotatedText = wx.deprecated(GraphicsContext.DrawText)") + + + c.includeCppCode('src/Point2D_helpers.cpp') + + # we'll reimplement this overload as StrokeLineSegments + c.find('StrokeLines').findOverload('beginPoints').ignore() + c.addCppMethod('void', 'StrokeLineSegments', '(PyObject* beginPoints, PyObject* endPoints)', + pyArgsString="(beginPoint2Ds, endPoint2Ds)", + doc="Stroke disconnected lines from begin to end points.", + body="""\ + size_t c1, c2, count; + wxPoint2D* beginP = wxPoint2D_array_helper(beginPoints, &c1); + wxPoint2D* endP = wxPoint2D_array_helper(endPoints, &c2); + + if ( beginP != NULL && endP != NULL ) { + count = wxMin(c1, c2); + self->StrokeLines(count, beginP, endP); + } + delete [] beginP; + delete [] endP; + """) + + # Also reimplement the main StrokeLines method to reuse the same helper + # function as StrokLineSegments + m = c.find('StrokeLines').findOverload('points').ignore() + c.addCppMethod('void', 'StrokeLines', '(PyObject* points)', + pyArgsString="(point2Ds)", + doc="Stroke lines conencting all the points.", + body="""\ + size_t count; + wxPoint2D* ptsArray = wxPoint2D_array_helper(points, &count); + + if ( ptsArray != NULL ) { + self->StrokeLines(count, ptsArray); + delete [] ptsArray; + } + """) + + # and once more for DrawLines + m = c.find('DrawLines').ignore() + c.addCppMethod('void', 'DrawLines', '(PyObject* points, wxPolygonFillMode fillStyle = wxODDEVEN_RULE)', + pyArgsString="(point2Ds, fillStyle=ODDEVEN_RULE)", + doc="Draws a polygon.", + body="""\ + size_t count; + wxPoint2D* ptsArray = wxPoint2D_array_helper(points, &count); + + if ( ptsArray != NULL ) { + self->DrawLines(count, ptsArray, fillStyle); + delete [] ptsArray; + } + """) + + #--------------------------------------------- c = module.find('wxGraphicsPath') tools.removeVirtuals(c) c.find('GetBox').findOverload('wxDouble *x, wxDouble *y').ignore() c.find('GetCurrentPoint').findOverload('wxDouble *x, wxDouble *y').ignore() + #--------------------------------------------- c = module.find('wxGraphicsRenderer') tools.removeVirtuals(c) markFactories(c) @@ -80,12 +182,43 @@ def run(): c.find('CreateContext').findOverload('wxEnhMetaFileDC').ignore() # See above - for m in [c.find('CreateFont')] + c.find('CreateFont').overloads: + for m in c.find('CreateFont').all(): m.find('col').default = 'BLACK' - + #--------------------------------------------- c = module.find('wxGraphicsMatrix') tools.removeVirtuals(c) + + c.find('Concat').overloads = [] + c.find('IsEqual').overloads = [] + + c.find('Get.a').out = True + c.find('Get.b').out = True + c.find('Get.c').out = True + c.find('Get.d').out = True + c.find('Get.tx').out = True + c.find('Get.ty').out = True + + c.find('TransformDistance.dx').inOut = True + c.find('TransformDistance.dy').inOut = True + + c.find('TransformPoint.x').inOut = True + c.find('TransformPoint.y').inOut = True + + + #--------------------------------------------- + c = module.find('wxGraphicsGradientStops') + c.addCppMethod('SIP_SSIZE_T', '__len__', '()', body="return (SIP_SSIZE_T)self->GetCount();") + c.addCppMethod('wxGraphicsGradientStop*', '__getitem__', '(size_t n)', + pyArgsString='(n)', + body="return new wxGraphicsGradientStop(self->Item(n));") + + + #--------------------------------------------- + # Use the pyNames we set for these classes in geometry.py so the old + # names do not show up in the docstrings, etc. + tools.changeTypeNames(module, 'wxPoint2DDouble', 'wxPoint2D') + tools.changeTypeNames(module, 'wxRect2DDouble', 'wxRect2D') #----------------------------------------------------------------- diff --git a/src/Point2D_helpers.cpp b/src/Point2D_helpers.cpp new file mode 100644 index 00000000..a5325abc --- /dev/null +++ b/src/Point2D_helpers.cpp @@ -0,0 +1,53 @@ + +#define sipType_wxPoint2D sipType_wxPoint2DDouble + +// Convert a Python sequence of 2-tuples of numbers or wx.Point2D objects into a +// C array of wxPoint2D instances +static +wxPoint2D* wxPoint2D_array_helper(PyObject* source, size_t *count) +{ + wxPoint2D* array; + Py_ssize_t idx, len; + + // ensure that it is a sequence + if (! PySequence_Check(source)) + goto error0; + // ensure it is not a string or unicode object (they are sequences too) + else if (PyString_Check(source) || PyUnicode_Check(source)) + goto error0; + // ensure each item can be converted to wxPoint2D + else { + len = PySequence_Length(source); + for (idx=0; idx( + sipConvertToType(obj, sipType_wxPoint2D, NULL, 0, &state, &err)); + array[idx] = *item; + sipReleaseType((void*)item, sipType_wxPoint2D, state); // delete temporary instances + Py_DECREF(obj); + } + return array; + +error0: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of length-2 sequences or wxPoint2D objects."); + return NULL; +} diff --git a/unittests/test_graphics.py b/unittests/test_graphics.py new file mode 100644 index 00000000..99da40ad --- /dev/null +++ b/unittests/test_graphics.py @@ -0,0 +1,155 @@ +import imp_unittest, unittest +import wtc +import wx +import os + +#--------------------------------------------------------------------------- + +class graphics_Tests(wtc.WidgetTestCase): + + def test_gcCreate1(self): + gc = wx.GraphicsContext.Create(wx.ClientDC(self.frame)) + self.assertTrue(gc.IsOk()) + + + def test_gcCreate2(self): + bmp = wx.Bitmap(100,100) + mdc = wx.MemoryDC(bmp) + gc = wx.GraphicsContext.Create(mdc) + self.assertTrue(gc.IsOk()) + + def test_gcCreate3(self): + img = wx.Image(100,100) + gc = wx.GraphicsContext.Create(img) + self.assertTrue(gc.IsOk()) + + def test_gcCreateBitmap(self): + gc = wx.GraphicsContext.Create(self.frame) + self.assertTrue(gc.IsOk()) + bmp = wx.Bitmap(100,100) + gb = gc.CreateBitmap(bmp) + self.assertTrue(gb.IsOk()) + self.assertTrue(isinstance(gb, wx.GraphicsBitmap)) + + img = wx.Image(100,100) + gb = gc.CreateBitmapFromImage(img) + self.assertTrue(gb.IsOk()) + gb = gc.CreateSubBitmap(gb, 5, 5, 25, 25) + self.assertTrue(gb.IsOk()) + + img = gb.ConvertToImage() + self.assertTrue(img.IsOk()) + + def test_gcCreateBrush(self): + gc = wx.GraphicsContext.Create(self.frame) + gb = gc.CreateBrush(wx.Brush('blue')) + self.assertTrue(gb.IsOk()) + self.assertTrue(isinstance(gb, wx.GraphicsBrush)) + + def test_gcCreateFont(self): + gc = wx.GraphicsContext.Create(self.frame) + gf = gc.CreateFont(wx.NORMAL_FONT) + self.assertTrue(gf.IsOk()) + self.assertTrue(isinstance(gf, wx.GraphicsFont)) + + gf = gc.CreateFont(15, 'Courier') + self.assertTrue(gf.IsOk()) + + def test_gcCreatePen(self): + gc = wx.GraphicsContext.Create(self.frame) + gp = gc.CreatePen(wx.RED_PEN) + self.assertTrue(gp.IsOk()) + self.assertTrue(isinstance(gp, wx.GraphicsPen)) + + + def test_gcCreatePath(self): + gc = wx.GraphicsContext.Create(self.frame) + p = gc.CreatePath() + self.assertTrue(p.IsOk()) + self.assertTrue(isinstance(p, wx.GraphicsPath)) + + def test_gcCreateMatrix(self): + gc = wx.GraphicsContext.Create(self.frame) + m = gc.CreateMatrix() + self.assertTrue(m.IsOk()) + self.assertTrue(isinstance(m, wx.GraphicsMatrix)) + + values = m.Get() + self.assertTrue(len(values) == 6) + + dx, dy = m.TransformDistance(5,6) + x,y = m.TransformPoint(7,8) + + + def test_gcTextExtents(self): + gc = wx.GraphicsContext.Create(self.frame) + gf = gc.CreateFont(wx.NORMAL_FONT) + gc.SetFont(gf) + + ext = gc.GetPartialTextExtents("Hello") + self.assertEqual(len(ext), 5) + + w, h, d, e = gc.GetFullTextExtent("Hello") + w, h = gc.GetTextExtent("Hello") + + + + def test_gcStrokeLines1(self): + gc = wx.GraphicsContext.Create(self.frame) + gc.SetPen(wx.Pen('blue', 2)) + + points = [ wx.Point2D(5,5), + wx.Point2D(50,5), + wx.Point2D(50,50), + wx.Point2D(5,5), + ] + gc.StrokeLines(points) + + def test_gcStrokeLines2(self): + gc = wx.GraphicsContext.Create(self.frame) + gc.SetPen(wx.Pen('blue', 2)) + points = [ (5,5), (50,5), wx.Point2D(50,50), (5,5) ] + gc.StrokeLines(points) + + def test_gcStrokeLines3(self): + gc = wx.GraphicsContext.Create(self.frame) + gc.SetPen(wx.Pen('blue', 2)) + + with self.assertRaises(TypeError): + points = [ (5,5), (50,5), 'not a point', (5,5) ] + gc.StrokeLines(points) + + def test_gcDrawLines(self): + gc = wx.GraphicsContext.Create(self.frame) + gc.SetPen(wx.Pen('blue', 2)) + points = [ (5,5), (50,5), wx.Point2D(50,50), (5,5) ] + gc.DrawLines(points) + + + def test_gcGradientStops(self): + gs1 = wx.GraphicsGradientStop('red', 0.25) + gs2 = wx.GraphicsGradientStop('green', 0.50) + gs3 = wx.GraphicsGradientStop('blue', 0.90) + + gs1.Colour + gs1.Position + + stops = wx.GraphicsGradientStops() + stops.Add(gs1) + stops.Add(gs2) + stops.Add('white', 0.75) + stops.Add(gs3) + + self.assertEqual(len(stops), 6) # 2 existing, plus 4 added + gs = stops[2] + self.assertTrue(gs.Position == 0.5) + + gc = wx.GraphicsContext.Create(self.frame) + b = gc.CreateLinearGradientBrush(0,0, 500, 100, stops) + + +#--------------------------------------------------------------------------- + + +if __name__ == '__main__': + unittest.main()