diff --git a/TODO.txt b/TODO.txt index 2f3a472f..4208b76b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -119,9 +119,6 @@ other dev stuff * Locate and/or add items for the various functions and things in Classic's _functions.i module. - * Add CppMethods to wx.DC for the "*List" methods (DrawLinesList, etc.) that - were in Classic. - * Add ETG scripts for these items in _core: * PseudoDC (not actually an ETG script, probably a .sip) diff --git a/docs/classic_vs_phoenix.txt b/docs/classic_vs_phoenix.txt index 3b2dd949..3e3621c6 100644 --- a/docs/classic_vs_phoenix.txt +++ b/docs/classic_vs_phoenix.txt @@ -588,23 +588,17 @@ DrawArcPoint :meth:`~DC.DrawArc` DrawBitmapPoint :meth:`~DC.DrawBitmap` DrawCheckMarkRect :meth:`~DC.DrawCheckMark` DrawCirclePoint :meth:`~DC.DrawCircle` -DrawEllipseList ``MISSING`` DrawEllipsePointSize :meth:`~DC.DrawEllipse` DrawEllipseRect :meth:`~DC.DrawEllipse` DrawEllipticArcPointSize :meth:`~DC.DrawEllipticArc` DrawIconPoint :meth:`~DC.DrawIcon` -DrawLineList ``MISSING`` DrawLinePoint :meth:`~DC.DrawLine` -DrawPointList ``MISSING`` DrawPointPoint :meth:`~DC.DrawPoint` -DrawPolygonList ``MISSING`` -DrawRectangleList ``MISSING`` DrawRectanglePointSize :meth:`~DC.DrawRectangle` DrawRectangleRect :meth:`~DC.DrawRectangle` DrawRotatedTextPoint :meth:`~DC.DrawRotatedText` DrawRoundedRectanglePointSize :meth:`~DC.DrawRoundedRectangle` DrawRoundedRectangleRect :meth:`~DC.DrawRoundedRectangle` -DrawTextList ``MISSING`` DrawTextPoint :meth:`~DC.DrawText` EndDrawing ``REMOVED`` FloodFillPoint :meth:`~DC.FloodFill` diff --git a/etg/dc.py b/etg/dc.py index d3d96cfd..4857ed9c 100644 --- a/etg/dc.py +++ b/etg/dc.py @@ -27,6 +27,8 @@ ITEMS = [ 'wxFontMetrics', 'wxDCFontChanger', ] +OTHERDEPS = [ 'src/dc_ex.cpp', ] + #--------------------------------------------------------------------------- def run(): @@ -169,39 +171,238 @@ def run(): c.addPyCode('DC.GetGdkDrawable = wx.deprecated(DC.GetGdkDrawable, "Use GetHandle instead.")') + # This file contains implementations of functions for quickly drawing + # lists of items on the DC. They are called from the CppMethods defined + # below, which in turn are called from the PyMethods below that. + c.includeCppCode('src/dc_ex.cpp') + + c.addCppMethod('PyObject*', '_DrawPointList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)', + body="return wxPyDrawXXXList(*self, wxPyDrawXXXPoint, pyCoords, pyPens, pyBrushes);") - # TODO: Port the wxPyDrawXXX code and the DrawXXXList methods from Classic - # TODO: Port the PseudoDC from Classic + c.addCppMethod('PyObject*', '_DrawLineList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)', + body="return wxPyDrawXXXList(*self, wxPyDrawXXXLine, pyCoords, pyPens, pyBrushes);") + + c.addCppMethod('PyObject*', '_DrawRectangleList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)', + body="return wxPyDrawXXXList(*self, wxPyDrawXXXRectangle, pyCoords, pyPens, pyBrushes);") + + c.addCppMethod('PyObject*', '_DrawEllipseList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)', + body="return wxPyDrawXXXList(*self, wxPyDrawXXXEllipse, pyCoords, pyPens, pyBrushes);") + + c.addCppMethod('PyObject*', '_DrawPolygonList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)', + body="return wxPyDrawXXXList(*self, wxPyDrawXXXPolygon, pyCoords, pyPens, pyBrushes);") + + c.addCppMethod('PyObject*', '_DrawTextList', + '(PyObject* textList, PyObject* pyPoints, PyObject* foregroundList, PyObject* backgroundList)', + body="return wxPyDrawTextList(*self, textList, pyPoints, foregroundList, backgroundList);") + c.addPyMethod('DrawPointList', '(self, points, pens=None)', + doc="""\ + Draw a list of points as quickly as possible. + + :param points: A sequence of 2-element sequences representing + each point to draw, (x,y). + :param pens: If None, then the current pen is used. If a single + pen then it will be used for all points. If a list of + pens then there should be one for each point in points. + """, + body="""\ + if pens is None: + pens = [] + elif isinstance(pens, wx.Pen): + pens = [pens] + elif len(pens) != len(points): + raise ValueError('points and pens must have same length') + return self._DrawPointList(points, pens, []) + """) + + c.addPyMethod('DrawLineList', '(self, lines, pens=None)', + doc="""\ + Draw a list of lines as quickly as possible. + + :param lines: A sequence of 4-element sequences representing + each line to draw, (x1,y1, x2,y2). + :param pens: If None, then the current pen is used. If a + single pen then it will be used for all lines. If + a list of pens then there should be one for each line + in lines. + """, + body="""\ + if pens is None: + pens = [] + elif isinstance(pens, wx.Pen): + pens = [pens] + elif len(pens) != len(lines): + raise ValueError('lines and pens must have same length') + return self._DrawLineList(lines, pens, []) + """) + + c.addPyMethod('DrawRectangleList', '(self, rectangles, pens=None, brushes=None)', + doc="""\ + Draw a list of rectangles as quickly as possible. + + :param rectangles: A sequence of 4-element sequences representing + each rectangle to draw, (x,y, w,h). + :param pens: If None, then the current pen is used. If a + single pen then it will be used for all rectangles. + If a list of pens then there should be one for each + rectangle in rectangles. + :param brushes: A brush or brushes to be used to fill the rectagles, + with similar semantics as the pens parameter. + """, + body="""\ + if pens is None: + pens = [] + elif isinstance(pens, wx.Pen): + pens = [pens] + elif len(pens) != len(rectangles): + raise ValueError('rectangles and pens must have same length') + if brushes is None: + brushes = [] + elif isinstance(brushes, wx.Brush): + brushes = [brushes] + elif len(brushes) != len(rectangles): + raise ValueError('rectangles and brushes must have same length') + return self._DrawRectangleList(rectangles, pens, brushes) + """) + + c.addPyMethod('DrawEllipseList', '(self, ellipses, pens=None, brushes=None)', + doc="""\ + Draw a list of ellipses as quickly as possible. + + :param ellipses: A sequence of 4-element sequences representing + each ellipse to draw, (x,y, w,h). + :param pens: If None, then the current pen is used. If a + single pen then it will be used for all ellipses. + If a list of pens then there should be one for each + ellipse in ellipses. + :param brushes: A brush or brushes to be used to fill the ellipses, + with similar semantics as the pens parameter. + """, + body="""\ + if pens is None: + pens = [] + elif isinstance(pens, wx.Pen): + pens = [pens] + elif len(pens) != len(ellipses): + raise ValueError('ellipses and pens must have same length') + if brushes is None: + brushes = [] + elif isinstance(brushes, wx.Brush): + brushes = [brushes] + elif len(brushes) != len(ellipses): + raise ValueError('ellipses and brushes must have same length') + return self._DrawEllipseList(ellipses, pens, brushes) + """) + + c.addPyMethod('DrawPolygonList', '(self, polygons, pens=None, brushes=None)', + doc="""\ + Draw a list of polygons, each of which is a list of points. + + :param polygons: A sequence of sequences of sequences. + [[(x1,y1),(x2,y2),(x3,y3)...], [(x1,y1),(x2,y2),(x3,y3)...]] + + :param pens: If None, then the current pen is used. If a + single pen then it will be used for all polygons. + If a list of pens then there should be one for each + polygon. + :param brushes: A brush or brushes to be used to fill the polygons, + with similar semantics as the pens parameter. + """, + body="""\ + if pens is None: + pens = [] + elif isinstance(pens, wx.Pen): + pens = [pens] + elif len(pens) != len(polygons): + raise ValueError('polygons and pens must have same length') + if brushes is None: + brushes = [] + elif isinstance(brushes, wx.Brush): + brushes = [brushes] + elif len(brushes) != len(polygons): + raise ValueError('polygons and brushes must have same length') + return self._DrawPolygonList(polygons, pens, brushes) + """) + + c.addPyMethod('DrawTextList', '(self, textList, coords, foregrounds=None, backgrounds=None)', + doc="""\ + Draw a list of strings using a list of coordinants for positioning each string. + + :param textList: A list of strings + :param coords: A list of (x,y) positions + :param foregrounds: A list of `wx.Colour` objects to use for the + foregrounds of the strings. + :param backgrounds: A list of `wx.Colour` objects to use for the + backgrounds of the strings. + + NOTE: Make sure you set background mode to wx.Solid (DC.SetBackgroundMode) + If you want backgrounds to do anything. + """, + body="""\ + if type(textList) == type(''): + textList = [textList] + elif len(textList) != len(coords): + raise ValueError('textlist and coords must have same length') + if foregrounds is None: + foregrounds = [] + elif isinstance(foregrounds, wx.Colour): + foregrounds = [foregrounds] + elif len(foregrounds) != len(coords): + raise ValueError('foregrounds and coords must have same length') + if backgrounds is None: + backgrounds = [] + elif isinstance(backgrounds, wx.Colour): + backgrounds = [backgrounds] + elif len(backgrounds) != len(coords): + raise ValueError('backgrounds and coords must have same length') + return self._DrawTextList(textList, coords, foregrounds, backgrounds) + """) + + + + + # TODO: Port the PseudoDC from Classic + + + + #----------------------------------------------------------------- c = module.find('wxDCClipper') assert isinstance(c, etgtools.ClassDef) c.addPrivateCopyCtor() # context manager methods c.addPyMethod('__enter__', '(self)', 'return self') c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False') + + #----------------------------------------------------------------- c = module.find('wxDCBrushChanger') assert isinstance(c, etgtools.ClassDef) c.addPrivateCopyCtor() # context manager methods c.addPyMethod('__enter__', '(self)', 'return self') c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False') + + #----------------------------------------------------------------- c = module.find('wxDCPenChanger') assert isinstance(c, etgtools.ClassDef) c.addPrivateCopyCtor() # context manager methods c.addPyMethod('__enter__', '(self)', 'return self') c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False') + + #----------------------------------------------------------------- c = module.find('wxDCTextColourChanger') assert isinstance(c, etgtools.ClassDef) c.addPrivateCopyCtor() # context manager methods c.addPyMethod('__enter__', '(self)', 'return self') c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False') + + #----------------------------------------------------------------- c = module.find('wxDCFontChanger') assert isinstance(c, etgtools.ClassDef) c.addPrivateCopyCtor() diff --git a/src/dc_ex.cpp b/src/dc_ex.cpp new file mode 100644 index 00000000..63178eec --- /dev/null +++ b/src/dc_ex.cpp @@ -0,0 +1,471 @@ +//-------------------------------------------------------------------------- +// Name: src/dc_ex.h +// Purpose: Functions that can quickly draw lists of items on a DC +// +// Author: Robin Dunn +// +// Created: 18-Aug-2012 +// Copyright: (c) 2012 by Total Control Software +// Licence: wxWindows license +//-------------------------------------------------------------------------- + + +typedef bool (*wxPyDrawListOp_t)(wxDC& dc, PyObject* coords); + +PyObject* wxPyDrawXXXList(wxDC& dc, wxPyDrawListOp_t doDraw, + PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes); + +bool wxPyDrawXXXPoint(wxDC& dc, PyObject* coords); +bool wxPyDrawXXXLine(wxDC& dc, PyObject* coords); +bool wxPyDrawXXXRectangle(wxDC& dc, PyObject* coords); +bool wxPyDrawXXXEllipse(wxDC& dc, PyObject* coords); +bool wxPyDrawXXXPolygon(wxDC& dc, PyObject* coords); + +PyObject* wxPyDrawTextList(wxDC& dc, PyObject* textList, PyObject* pyPoints, + PyObject* foregroundList, PyObject* backgroundList); + +//-------------------------------------------------------------------------- + + +PyObject* wxPyDrawXXXList(wxDC& dc, wxPyDrawListOp_t doDraw, + PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes) +{ + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + + bool isFastSeq = PyList_Check(pyCoords) || PyTuple_Check(pyCoords); + bool isFastPens = PyList_Check(pyPens) || PyTuple_Check(pyPens); + bool isFastBrushes = PyList_Check(pyBrushes) || PyTuple_Check(pyBrushes); + int numObjs = 0; + int numPens = 0; + int numBrushes = 0; + wxPen* pen; + wxBrush* brush; + PyObject* obj; + PyObject* coords; + int i = 0; + PyObject* retval; + + if (!PySequence_Check(pyCoords)) { + goto err0; + } + if (!PySequence_Check(pyPens)) { + goto err1; + } + if (!PySequence_Check(pyBrushes)) { + goto err2; + } + numObjs = PySequence_Length(pyCoords); + numPens = PySequence_Length(pyPens); + numBrushes = PySequence_Length(pyBrushes); + for (i = 0; i < numObjs; i++) { + // Use a new pen? + if (i < numPens) { + if (isFastPens) { + obj = PySequence_Fast_GET_ITEM(pyPens, i); + } + else { + obj = PySequence_GetItem(pyPens, i); + } + if (! wxPyConvertWrappedPtr(obj, (void **) &pen, "wxPen")) { + if (!isFastPens) + Py_DECREF(obj); + goto err1; + } + + dc.SetPen(*pen); + if (!isFastPens) + Py_DECREF(obj); + } + // Use a new brush? + if (i < numBrushes) { + if (isFastBrushes) { + obj = PySequence_Fast_GET_ITEM(pyBrushes, i); + } + else { + obj = PySequence_GetItem(pyBrushes, i); + } + if (!wxPyConvertWrappedPtr(obj, (void **) &brush, "wxBrush")) { + if (!isFastBrushes) + Py_DECREF(obj); + goto err2; + } + + dc.SetBrush(*brush); + if (!isFastBrushes) + Py_DECREF(obj); + } + + // Get the Coordinates + if (isFastSeq) { + coords = PySequence_Fast_GET_ITEM(pyCoords, i); + } + else { + coords = PySequence_GetItem(pyCoords, i); + } + + + // call the drawOp + bool success = doDraw(dc, coords); + if (!isFastSeq) + Py_DECREF(coords); + + if (! success) { + retval = NULL; + goto exit; + } + + } // end of main for loop + + Py_INCREF(Py_None); + retval = Py_None; + goto exit; + + + err0: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of coordinates"); + retval = NULL; + goto exit; + + err1: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of wxPens"); + retval = NULL; + goto exit; + + err2: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of wxBrushes"); + retval = NULL; + goto exit; + + + exit: + wxPyEndBlockThreads(blocked); + return retval; +} + + + +bool wxPyDrawXXXPoint(wxDC& dc, PyObject* coords) +{ + int x, y; + + if (! wxPy2int_seq_helper(coords, &x, &y)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of (x,y) sequences."); + return false; + } + dc.DrawPoint(x, y); + return true; +} + +bool wxPyDrawXXXLine(wxDC& dc, PyObject* coords) +{ + int x1, y1, x2, y2; + + if (! wxPy4int_seq_helper(coords, &x1, &y1, &x2, &y2)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of (x1,y1, x1,y2) sequences."); + return false; + } + dc.DrawLine(x1,y1, x2,y2); + return true; +} + +bool wxPyDrawXXXRectangle(wxDC& dc, PyObject* coords) +{ + int x, y, w, h; + + if (! wxPy4int_seq_helper(coords, &x, &y, &w, &h)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of (x,y, w,h) sequences."); + return false; + } + dc.DrawRectangle(x, y, w, h); + return true; +} + +bool wxPyDrawXXXEllipse(wxDC& dc, PyObject* coords) +{ + int x, y, w, h; + + if (! wxPy4int_seq_helper(coords, &x, &y, &w, &h)) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of (x,y, w,h) sequences."); + return false; + } + dc.DrawEllipse(x, y, w, h); + return true; +} + + +wxPoint* wxPoint_LIST_helper(PyObject* source, int *count); + +bool wxPyDrawXXXPolygon(wxDC& dc, PyObject* coords) +{ + wxPoint* points; + int numPoints; + + points = wxPoint_LIST_helper(coords, &numPoints); + if (! points) { + PyErr_SetString(PyExc_TypeError, "Expected a sequence of sequences of (x,y) sequences."); + return false; + } + dc.DrawPolygon(numPoints, points); + delete [] points; + return true; +} + + +//--------------------------------------------------------------------------- + + + +PyObject* wxPyDrawTextList(wxDC& dc, PyObject* textList, PyObject* pyPoints, PyObject* foregroundList, PyObject* backgroundList) +{ + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + + bool isFastSeq = PyList_Check(pyPoints) || PyTuple_Check(pyPoints); + bool isFastText = PyList_Check(textList) || PyTuple_Check(textList); + bool isFastForeground = PyList_Check(foregroundList) || PyTuple_Check(foregroundList); + bool isFastBackground = PyList_Check(backgroundList) || PyTuple_Check(backgroundList); + int numText = 0; + int numPoints = 0; + int numForeground = 0; + int numBackground = 0; + PyObject* obj; + int x1, y1; + int i = 0; + wxColor* color; + PyObject* retval; + wxString string; + + if (!PySequence_Check(pyPoints)) { + goto err0; + } + if (!PySequence_Check(textList)) { + goto err1; + } + if (!PySequence_Check(foregroundList)) { + goto err2; + } + if (!PySequence_Check(backgroundList)) { + goto err3; + } + numPoints = PySequence_Length(pyPoints); + numText = PySequence_Length(textList); + numForeground = PySequence_Length(foregroundList); + numBackground = PySequence_Length(backgroundList); + + for (i = 0; i < numPoints; i++) { + // Use a new string ? + if (i < numText) { + if ( isFastText ) { + obj = PySequence_Fast_GET_ITEM(textList, i); + } + else { + obj = PySequence_GetItem(textList, i); + } + if (! PyBytes_Check(obj) && !PyUnicode_Check(obj) ) { + Py_DECREF(obj); + goto err1; + } + string = Py2wxString(obj); + if ( !isFastText ) + Py_DECREF(obj); + } + + if (i < numForeground) { + // Use a new foreground ? + if ( isFastForeground ) { + obj = PySequence_Fast_GET_ITEM(foregroundList, i); + } + else { + obj = PySequence_GetItem(foregroundList, i); + } + if (! wxPyConvertWrappedPtr(obj, (void **) &color, "wxColour")) { + if (!isFastForeground) + Py_DECREF(obj); + goto err2; + } + dc.SetTextForeground(*color); + if ( !isFastForeground ) + Py_DECREF(obj); + } + + if (i < numBackground) { + // Use a new background ? + if ( isFastBackground ) { + obj = PySequence_Fast_GET_ITEM(backgroundList, i); + } + else { + obj = PySequence_GetItem(backgroundList, i); + } + if (! wxPyConvertWrappedPtr(obj, (void **) &color, "wxColour")) { + if (!isFastBackground) + Py_DECREF(obj); + goto err3; + } + dc.SetTextBackground(*color); + if ( !isFastBackground ) + Py_DECREF(obj); + } + + // Get the point coordinates + if (isFastSeq) { + obj = PySequence_Fast_GET_ITEM(pyPoints, i); + } + else { + obj = PySequence_GetItem(pyPoints, i); + } + if (! wxPy2int_seq_helper(obj, &x1, &y1)) { + if (! isFastSeq) + Py_DECREF(obj); + goto err0; + } + if (PyErr_Occurred()) { + retval = NULL; + if (!isFastSeq) + Py_DECREF(obj); + goto exit; + } + + + // Now draw the text + dc.DrawText(string, x1, y1); + + if (!isFastText) + Py_DECREF(obj); + } + + Py_INCREF(Py_None); + retval = Py_None; + goto exit; + + err0: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of (x,y) sequences."); + retval = NULL; + goto exit; + err1: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of strings"); + retval = NULL; + goto exit; + + err2: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of wxColours for foregrounds"); + retval = NULL; + goto exit; + + err3: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of wxColours for backgrounds"); + retval = NULL; + goto exit; + + exit: + wxPyEndBlockThreads(blocked); + return retval; +} + +//--------------------------------------------------------------------------- + + +bool wxPointFromObjects(PyObject* o1, PyObject* o2, wxPoint* point) +{ + // get the x value + if (wxPyInt_Check(o1)) + point->x = (int)wxPyInt_AS_LONG(o1); + else if (PyFloat_Check(o1)) + point->x = (int)PyFloat_AS_DOUBLE(o1); + else if (PyNumber_Check(o1)) + point->x = (int)wxPyInt_AsLong(o1); + else + return false; + + // get the y value + if (wxPyInt_Check(o2)) + point->y = (int)wxPyInt_AS_LONG(o2); + else if (PyFloat_Check(o2)) + point->y = (int)PyFloat_AS_DOUBLE(o2); + else if (PyNumber_Check(o2)) + point->y = (int)wxPyInt_AsLong(o2); + else + return false; + + return true; +} + + +wxPoint* wxPoint_LIST_helper(PyObject* source, int *count) +{ + int idx; + wxPoint* temp; + PyObject *o, *o1, *o2; + bool isFast = PyList_Check(source) || PyTuple_Check(source); + + if (!PySequence_Check(source)) { + goto error0; + } + + // The length of the sequence is returned in count. + *count = PySequence_Length(source); + if (*count < 0) { + goto error0; + } + + temp = new wxPoint[*count]; + if (!temp) { + PyErr_SetString(PyExc_MemoryError, "Unable to allocate temporary array"); + return NULL; + } + for (idx=0; idx<*count; idx++) { + // Get an item: try fast way first. + if (isFast) { + o = PySequence_Fast_GET_ITEM(source, idx); + } + else { + o = PySequence_GetItem(source, idx); + if (o == NULL) { + goto error1; + } + } + + // Convert o to wxPoint. + if ((PyTuple_Check(o) && PyTuple_GET_SIZE(o) == 2) || + (PyList_Check(o) && PyList_GET_SIZE(o) == 2)) { + o1 = PySequence_Fast_GET_ITEM(o, 0); + o2 = PySequence_Fast_GET_ITEM(o, 1); + if (!wxPointFromObjects(o1, o2, &temp[idx])) { + goto error2; + } + } + else if (wxPyWrappedPtr_Check(o)) { + wxPoint* pt; + if (! wxPyConvertWrappedPtr(o, (void **)&pt, "wxPoint")) { + goto error2; + } + temp[idx] = *pt; + } + else if (PySequence_Check(o) && PySequence_Length(o) == 2) { + o1 = PySequence_GetItem(o, 0); + o2 = PySequence_GetItem(o, 1); + if (!wxPointFromObjects(o1, o2, &temp[idx])) { + goto error3; + } + Py_DECREF(o1); + Py_DECREF(o2); + } + else { + goto error2; + } + // Clean up. + if (!isFast) + Py_DECREF(o); + } + return temp; + +error3: + Py_DECREF(o1); + Py_DECREF(o2); +error2: + if (!isFast) + Py_DECREF(o); +error1: + delete [] temp; +error0: + PyErr_SetString(PyExc_TypeError, "Expected a sequence of length-2 sequences or wx.Points."); + return NULL; +} diff --git a/src/wxpy_api.h b/src/wxpy_api.h index 37184a15..db888cca 100644 --- a/src/wxpy_api.h +++ b/src/wxpy_api.h @@ -148,10 +148,15 @@ struct wxPyAPI { PyObject* (*p_wxPyConstructObject)(void* ptr, const wxString& className, bool setThisOwn); wxPyBlock_t (*p_wxPyBeginBlockThreads)(); void (*p_wxPyEndBlockThreads)(wxPyBlock_t blocked); + bool (*p_wxPyWrappedPtr_Check)(PyObject* obj); + bool (*p_wxPyConvertWrappedPtr)(PyObject* obj, void **ptr, const wxString& className); + bool (*p_wxPy2int_seq_helper)(PyObject* source, int* i1, int* i2); + bool (*p_wxPy4int_seq_helper)(PyObject* source, int* i1, int* i2, int* i3, int* i4); // Always add new items here at the end. }; + inline wxPyAPI* wxPyGetAPIPtr() { static wxPyAPI* wxPyAPIPtr = NULL; @@ -182,6 +187,16 @@ inline wxString Py2wxString(PyObject* source) inline PyObject* wxPyConstructObject(void* ptr, const wxString& className, bool setThisOwn=false) { return wxPyGetAPIPtr()->p_wxPyConstructObject(ptr, className, setThisOwn); } + +// Check if a PyObject is a wrapped type +inline bool wxPyWrappedPtr_Check(PyObject* obj) + { return wxPyGetAPIPtr()->p_wxPyWrappedPtr_Check(obj); } + + +// Convert a wrapped SIP object to its C++ pointer, ensuring that it is of the expected type +inline bool wxPyConvertWrappedPtr(PyObject* obj, void **ptr, const wxString& className) + { return wxPyGetAPIPtr()->p_wxPyConvertWrappedPtr(obj, ptr, className); } + // Calls from wxWindows back to Python code, or even any PyObject // manipulations, PyDECREF's and etc. should be wrapped in calls to these functions: @@ -192,6 +207,14 @@ inline void wxPyEndBlockThreads(wxPyBlock_t blocked) { wxPyGetAPIPtr()->p_wxPyEndBlockThreads(blocked); } + +// A helper for converting a 2 element sequence to a pair of integers +inline bool wxPy2int_seq_helper(PyObject* source, int* i1, int* i2) + { return wxPyGetAPIPtr()->p_wxPy2int_seq_helper(source, i1, i2); } + +// A helper for converting a 4 element sequence to a set of integers +inline bool wxPy4int_seq_helper(PyObject* source, int* i1, int* i2, int* i3, int* i4) + { return wxPyGetAPIPtr()->p_wxPy4int_seq_helper(source, i1, i2, i3, i4); } diff --git a/src/wxpy_api.sip b/src/wxpy_api.sip index d6849ad4..70a1fd8c 100644 --- a/src/wxpy_api.sip +++ b/src/wxpy_api.sip @@ -104,6 +104,25 @@ static PyObject* i_wxPyConstructObject(void* ptr, } +// Check if a PyObject is a wrapped type +static bool i_wxPyWrappedPtr_Check(PyObject* obj) +{ + return PyObject_TypeCheck(obj, sipWrapper_Type); +} + + +// Convert a wrapped SIP object to its C++ pointer, ensuring that it is of the expected type +static bool i_wxPyConvertWrappedPtr(PyObject* obj, void **ptr, const wxString& className) +{ + const sipTypeDef* td = sipFindType(className); + if (!td) + return false; + if (! sipCanConvertToType(obj, td, SIP_NO_CONVERTORS)) + return false; + int sipIsErr = 0; + *ptr = sipConvertToType(obj, td, NULL, SIP_NO_CONVERTORS, 0, &sipIsErr); + return true; +} // Calls from wxWindows back to Python code, or even any PyObject @@ -126,12 +145,83 @@ static void i_wxPyEndBlockThreads(wxPyBlock_t blocked) } +// A helper for converting a 2 element sequence to a pair of integers +static bool i_wxPy2int_seq_helper(PyObject* source, int* i1, int* i2) +{ + bool isFast = PyList_Check(source) || PyTuple_Check(source); + PyObject *o1, *o2; + + if (!PySequence_Check(source) || PySequence_Length(source) != 2) + return false; + + if (isFast) { + o1 = PySequence_Fast_GET_ITEM(source, 0); + o2 = PySequence_Fast_GET_ITEM(source, 1); + } + else { + o1 = PySequence_GetItem(source, 0); + o2 = PySequence_GetItem(source, 1); + } + + *i1 = wxPyInt_AsLong(o1); + *i2 = wxPyInt_AsLong(o2); + + if (! isFast) { + Py_DECREF(o1); + Py_DECREF(o2); + } + return true; +} + +// A helper for converting a 4 element sequence to a set of integers +static bool i_wxPy4int_seq_helper(PyObject* source, int* i1, int* i2, int* i3, int* i4) +{ + bool isFast = PyList_Check(source) || PyTuple_Check(source); + PyObject *o1, *o2, *o3, *o4; + + if (!PySequence_Check(source) || PySequence_Length(source) != 4) + return false; + + if (isFast) { + o1 = PySequence_Fast_GET_ITEM(source, 0); + o2 = PySequence_Fast_GET_ITEM(source, 1); + o3 = PySequence_Fast_GET_ITEM(source, 2); + o4 = PySequence_Fast_GET_ITEM(source, 3); + } + else { + o1 = PySequence_GetItem(source, 0); + o2 = PySequence_GetItem(source, 1); + o3 = PySequence_GetItem(source, 2); + o4 = PySequence_GetItem(source, 3); + } + + *i1 = wxPyInt_AsLong(o1); + *i2 = wxPyInt_AsLong(o2); + *i3 = wxPyInt_AsLong(o3); + *i4 = wxPyInt_AsLong(o4); + + if (! isFast) { + Py_DECREF(o1); + Py_DECREF(o2); + Py_DECREF(o3); + Py_DECREF(o4); + } + return true; +} + + + + // An instance of the API structure static wxPyAPI API = { i_Py2wxString, i_wxPyConstructObject, i_wxPyBeginBlockThreads, - i_wxPyEndBlockThreads + i_wxPyEndBlockThreads, + i_wxPyWrappedPtr_Check, + i_wxPyConvertWrappedPtr, + i_wxPy2int_seq_helper, + i_wxPy4int_seq_helper }; %End diff --git a/unittests/test_dcDrawLists.py b/unittests/test_dcDrawLists.py new file mode 100644 index 00000000..b8406631 --- /dev/null +++ b/unittests/test_dcDrawLists.py @@ -0,0 +1,278 @@ +import imp_unittest, unittest +import wtc +import wx +import random +try: + import numpy as np + haveNumpy = True +except ImportError: + haveNumpy = False + +#--------------------------------------------------------------------------- + +w = 600 +h = 400 +num = 500 + +colornames = ["BLACK", + "BLUE", + "BLUE VIOLET", + "BROWN", + "CYAN", + "DARK GREY", + "DARK GREEN", + "GOLD", + "GREY", + "GREEN", + "MAGENTA", + "NAVY", + "PINK", + "RED", + "SKY BLUE", + "VIOLET", + "YELLOW", + ] +pencache = {} +brushcache = {} + +def makeRandomPoints(): + pnts = [] + + for i in range(num): + x = random.randint(0, w) + y = random.randint(0, h) + pnts.append( (x,y) ) + + return pnts + + +def makeRandomLines(): + lines = [] + + for i in range(num): + x1 = random.randint(0, w) + y1 = random.randint(0, h) + x2 = random.randint(0, w) + y2 = random.randint(0, h) + lines.append( (x1,y1, x2,y2) ) + + return lines + + +def makeRandomRectangles(): + rects = [] + + for i in range(num): + W = random.randint(10, w/2) + H = random.randint(10, h/2) + x = random.randint(0, w - W) + y = random.randint(0, h - H) + rects.append( (x, y, W, H) ) + + return rects + + +def makeRandomPolygons(): + Np = 8 # number of points per polygon + polys = [] + + for i in range(num): + poly = [] + + for i in range(Np): + x = random.randint(0, w) + y = random.randint(0, h) + poly.append( (x,y) ) + + polys.append( poly ) + + return polys + + +def makeRandomText(): + Np = 8 # number of characters in text + text = [] + + for i in range(num): + word = [] + + for i in range(Np): + c = chr( random.randint(48, 122) ) + word.append( c ) + + text.append( "".join(word) ) + + return text + + +def makeRandomColors(): + colors = [] + for i in range(num): + c = random.choice(colornames) + colors.append(wx.Colour(c)) + return colors + + +def makeRandomPens(): + pens = [] + for i in range(num): + c = random.choice(colornames) + t = random.randint(1, 4) + if not pencache.has_key( (c, t) ): + pencache[(c, t)] = wx.Pen(c, t) + pens.append( pencache[(c, t)] ) + return pens + + +def makeRandomBrushes(): + brushes = [] + for i in range(num): + c = random.choice(colornames) + if not brushcache.has_key(c): + brushcache[c] = wx.Brush(c) + brushes.append( brushcache[c] ) + return brushes + + +#--------------------------------------------------------------------------- + + +class dcDrawLists_Tests(wtc.WidgetTestCase): + + def test_dcDrawPointLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + + pens = makeRandomPens() + + dc.DrawPointList(makeRandomPoints()) + dc.DrawPointList(makeRandomPoints(), wx.Pen("RED", 1)) + dc.DrawPointList(makeRandomPoints(), pens) + del dc + + + @unittest.skipIf(not haveNumpy, "Numpy required for this test") + def test_dcDrawPointArray(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + + pens = makeRandomPens() + + dc.DrawPointList(np.array(makeRandomPoints())) + dc.DrawPointList(np.array(makeRandomPoints()), wx.Pen("RED", 1)) + dc.DrawPointList(np.array(makeRandomPoints()), pens) + del dc + + + def test_dcDrawLineLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + + pens = makeRandomPens() + + dc.DrawLineList(makeRandomLines()) + dc.DrawLineList(makeRandomLines(), wx.Pen("RED", 2)) + dc.DrawLineList(makeRandomLines(), pens) + del dc + + + def test_dcDrawRectangleLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + dc.SetBrush( wx.Brush("RED") ) + + pens = makeRandomPens() + brushes = makeRandomBrushes() + + dc.DrawRectangleList(makeRandomRectangles()) + dc.DrawRectangleList(makeRandomRectangles(),pens) + dc.DrawRectangleList(makeRandomRectangles(),pens[0],brushes) + dc.DrawRectangleList(makeRandomRectangles(),pens,brushes[0]) + dc.DrawRectangleList(makeRandomRectangles(),None,brushes) + del dc + + + @unittest.skipIf(not haveNumpy, "Numpy required for this test") + def test_dcDrawRectangleArray(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + dc.SetBrush( wx.Brush("RED") ) + + pens = makeRandomPens() + brushes = makeRandomBrushes() + + dc.DrawRectangleList(np.array(makeRandomRectangles())) + dc.DrawRectangleList(np.array(makeRandomRectangles()),pens) + dc.DrawRectangleList(np.array(makeRandomRectangles()),pens[0],brushes) + dc.DrawRectangleList(np.array(makeRandomRectangles()),pens,brushes[0]) + dc.DrawRectangleList(np.array(makeRandomRectangles()),None,brushes) + del dc + + + def test_dcDrawElipseLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + dc.SetBrush( wx.Brush("RED") ) + + pens = makeRandomPens() + brushes = makeRandomBrushes() + + dc.DrawEllipseList(makeRandomRectangles()) + dc.DrawEllipseList(makeRandomRectangles(),pens) + dc.DrawEllipseList(makeRandomRectangles(),pens[0],brushes) + dc.DrawEllipseList(makeRandomRectangles(),pens,brushes[0]) + dc.DrawEllipseList(makeRandomRectangles(),None,brushes) + del dc + + + def test_dcDrawPloygonLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetPen(wx.Pen("BLACK", 1)) + dc.SetBrush( wx.Brush("RED") ) + + pens = makeRandomPens() + brushes = makeRandomBrushes() + polygons = makeRandomPolygons() + + dc.DrawPolygonList(polygons) + dc.DrawPolygonList(polygons, pens) + dc.DrawPolygonList(polygons, pens[0],brushes) + dc.DrawPolygonList(polygons, pens,brushes[0]) + dc.DrawPolygonList(polygons, None,brushes) + del dc + + + def test_dcDrawTextLists(self): + pnl = wx.Panel(self.frame) + self.frame.SetSize((w,h)) + dc = wx.ClientDC(pnl) + dc.SetBackgroundMode(wx.SOLID) + + points = makeRandomPoints() + fore = makeRandomColors() + back = makeRandomColors() + texts = makeRandomText() + + dc.DrawTextList(texts, points, fore, back) + del dc + + + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main()