diff --git a/TODO.txt b/TODO.txt index 3c9e25c0..17b8ff98 100644 --- a/TODO.txt +++ b/TODO.txt @@ -129,7 +129,6 @@ other dev stuff * joystick * sound * mimetype - * finish dataobj * power * overlay * PseudoDC diff --git a/docs/MigrationGuide.txt b/docs/MigrationGuide.txt index 13f8298c..7b21ee40 100644 --- a/docs/MigrationGuide.txt +++ b/docs/MigrationGuide.txt @@ -319,3 +319,42 @@ other platforms) has been changed to not accept the cursor/icon in the constructors. Instead you'll have to call either SetCursor or SetIcon depending on the platform. + + +wx.DataObject and derived classes +--------------------------------- + +The wx.DataObject and wx.DataObjectSimple classes can now be +subclassed in Python. wx.DataObject will let you proivide complex +multi-format data objects that do not need to copy the data until one +of the formats is requested from the clipboard or a DnD operation. +wx.DataObjectSimple is a simplification that only deals with one data +format, (although multiple objects can still be provided with +wx.DataObjectComposite.) + +Python buffer objects are used for transferring data to/from the +clipboard or DnD partner. Anything that supports the buffer protocol +can be used for setting or providing data, and a memoryview object is +created for the APIs where the data object should fetch from or copy +to a specific memory location. Here is a simple example:: + + class MyDataObject(wx.DataObjectSimple): + def __init__(self, value=''): + wx.DataObjectSimple.__init__(self) + self.SetFormat(wx.DataFormat("my data format")) + self.myData = bytes(value) + + def GetDataSize(self): + return len(self.myData) + + def GetDataHere(self, buf): + # copy our local data value to buf + assert isinstance(buf, memoryview) + buf[:] = self.myData + return True + + def SetData(self, buf): + # copy from buf to our local data value + assert isinstance(buf, memoryview) + self.myData = buf.tobytes() + return True diff --git a/etg/dataobj.py b/etg/dataobj.py index 2ba9da3b..bad462dd 100644 --- a/etg/dataobj.py +++ b/etg/dataobj.py @@ -19,17 +19,86 @@ DOCSTRING = "" # this script. ITEMS = [ 'wxDataFormat', 'wxDataObject', - #'wxDataObjectSimple', - #'wxCustomDataObject', - #'wxDataObjectComposite', - #'wxBitmapDataObject', - #'wxTextDataObject', - #'wxURLDataObject', - #'wxFileDataObject', + 'wxDataObjectSimple', + 'wxCustomDataObject', + 'wxDataObjectComposite', + 'wxBitmapDataObject', + 'wxTextDataObject', + 'wxURLDataObject', + 'wxFileDataObject', ] -# TODO: apply some tweaks similar to how Classic implements these classes + +#--------------------------------------------------------------------------- + +def addGetAllFormats(klass, pureVirtual=False): + # Replace the GetAllFormats method with an implementation that returns + # the formats as a Python list + m = klass.findItem('GetAllFormats') + if m: + m.ignore() + klass.addCppMethod('PyObject*', 'GetAllFormats', '(wxDataObject::Direction dir=wxDataObject::Get)', + cppSignature='void (wxDataFormat* formats, Direction dir)', + isVirtual=True, + isPureVirtual=pureVirtual, + isConst=True, + doc="""\ + Returns a list of wx.DataFormat objects which this data object supports + transfering in the given direction.""", + body="""\ + size_t count = self->GetFormatCount(dir); + wxDataFormat* formats = new wxDataFormat[count]; + self->GetAllFormats(formats, dir); + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + PyObject* list = PyList_New(count); + for (size_t i=0; i(self->GetType());") + item = module.find('wxFormatInvalid') + module.items.remove(item) + module.insertItemAfter(c, item) + + #------------------------------------------------------------ c = module.find('wxDataObject') - c.abstract = True + c.addPrivateCopyCtor() + + addGetAllFormats(c, True) + + # For initial testing only. TODO: Remove later + c.addPublic() + c.addCppMethod('void', '_testGetAllFormats', '()', + body="""\ + size_t count = self->GetFormatCount(); + wxDataFormat fmts[count]; + self->GetAllFormats(fmts); + """) + + # Replace the GetDataHere method with a version that uses a smarter + # Python buffer object instead of a stupid void pointer. + c.find('GetDataHere').ignore() + c.addCppMethod('bool', 'GetDataHere', '(const wxDataFormat& format, wxPyBuffer* buf)', + cppSignature='bool (const wxDataFormat& format, void* buf)', + isVirtual=True, isPureVirtual=True, + isConst=True, + doc="Copies this data object's data in the requested format to the buffer provided.", + body="""\ + if (!buf->checkSize(self->GetDataSize(*format))) + return false; + return self->GetDataHere(*format, buf->m_ptr); + """, + + # This code will be used in the function that calls a Python implementation + # of this method. + virtualCatcherCode="""\ + // Call self.GetDataSize() to find out how big the buffer should be + PyObject* self = NULL; + PyObject* fmtObj = NULL; + PyObject* sizeObj = NULL; + PyObject* buffer = NULL; + PyObject* resObj = NULL; + Py_ssize_t size = 0; + + self = PyMethod_Self(sipMethod); // this shouldn't fail, and the reference is borrowed + + fmtObj = wxPyConstructObject((void*)&format, "wxDataFormat", false); + if (!fmtObj) goto error; + sizeObj = PyObject_CallMethod(self, "GetDataSize", "(O)", fmtObj, NULL); + if (!sizeObj) goto error; + size = PyInt_AsSsize_t(sizeObj); + + // Make a buffer that big using the pointer passed to us, and then + // call the Python method. + buffer = wxPyMakeBuffer(buf, size); + resObj = sipCallMethod(0, sipMethod, "SS", fmtObj, buffer); + + if (!resObj || sipParseResult(0,sipMethod,resObj,"b",&sipRes) < 0) + PyErr_Print(); + + error: + Py_XDECREF(resObj); + Py_XDECREF(buffer); + Py_XDECREF(fmtObj); + Py_XDECREF(sizeObj); + """) + # Replace the SetData method with an implementation that uses Python + # buffer objects. + c.find('SetData').ignore() + c.addCppMethod('bool', 'SetData', '(const wxDataFormat& format, wxPyBuffer* buf)', + cppSignature='bool (const wxDataFormat& format, size_t len, const void* buf)', + isVirtual=True, + doc="Copies data from the provided buffer to this data object for the specified format.", + body="return self->SetData(*format, buf->m_len, buf->m_ptr);", + + # This code will be used in the function that calls a Python implementation + # of this method. + virtualCatcherCode="""\ + PyObject* buffer = wxPyMakeBuffer((void*)buf, len); + PyObject *resObj = sipCallMethod(0,sipMethod,"NS", + new wxDataFormat(format),sipType_wxDataFormat,NULL, + buffer); + if (!resObj || sipParseResult(0,sipMethod,resObj,"b",&sipRes) < 0) + PyErr_Print(); + Py_XDECREF(resObj); + Py_XDECREF(buffer); + """) + + + #------------------------------------------------------------ + c = module.find('wxDataObjectSimple') + + c.addCppCtor_sip('(const wxString& formatName)', + body='sipCpp = new sipwxDataObjectSimple(wxDataFormat(*formatName));') + + # As in wxDataObject above replace GetDataHere and SetData with methods + # that use buffer objects instead of void*, but this time we do not pass + # a DataFormat object with it. + c.find('GetDataHere').ignore() + c.addCppMethod('bool', 'GetDataHere', '(wxPyBuffer* buf)', + cppSignature='bool (void* buf)', + isVirtual=True, + isConst=True, + doc="Copies this data object's data bytes to the given buffer", + body="""\ + if (!buf->checkSize(self->GetDataSize())) + return false; + return self->GetDataHere(buf->m_ptr); + """, + virtualCatcherCode="""\ + // Call self.GetDataSize() to find out how big the buffer should be + PyObject* self = NULL; + PyObject* sizeObj = NULL; + PyObject* buffer = NULL; + PyObject* resObj = NULL; + Py_ssize_t size = 0; + + self = PyMethod_Self(sipMethod); + + sizeObj = PyObject_CallMethod(self, "GetDataSize", "", NULL); + if (!sizeObj) goto error; + size = PyInt_AsSsize_t(sizeObj); + + // Make a buffer that big using the pointer passed to us, and then + // call the Python method. + buffer = wxPyMakeBuffer(buf, size); + resObj = sipCallMethod(0, sipMethod, "S", buffer); + + if (!resObj || sipParseResult(0,sipMethod,resObj,"b",&sipRes) < 0) + PyErr_Print(); + + error: + Py_XDECREF(resObj); + Py_XDECREF(buffer); + Py_XDECREF(sizeObj); + """) + + c.find('SetData').ignore() + c.addCppMethod('bool', 'SetData', '(wxPyBuffer* buf)', + cppSignature='bool (size_t len, const void* buf)', + isVirtual=True, + doc="Copies data from the provided buffer to this data object.", + body="return self->SetData(buf->m_len, buf->m_ptr);", + virtualCatcherCode="""\ + PyObject* buffer = wxPyMakeBuffer((void*)buf, len); + PyObject *resObj = sipCallMethod(0,sipMethod,"S",buffer); + if (!resObj || sipParseResult(0,sipMethod,resObj,"b",&sipRes) < 0) + PyErr_Print(); + Py_XDECREF(resObj); + Py_XDECREF(buffer); + """) + + addGetAllFormats(c) + + # We need to let SIP know that the pure virtuals in the base class have + # impelmentations here even though they will not be used much (if at + # all.) Those that are overridden in this class with different signatures + # we'll just mark as private to help avoid confusion. + c.addItem(etgtools.WigCode(code="""\ + virtual size_t GetFormatCount(Direction dir = Get) const; + virtual wxDataFormat GetPreferredFormat(Direction dir = Get) const; + private: + virtual size_t GetDataSize(const wxDataFormat& format) const; + virtual bool GetDataHere(const wxDataFormat& format, void* buf) const; + virtual bool SetData(const wxDataFormat& format, size_t len, const void* buf); + """)) + + + + #------------------------------------------------------------ + c = module.find('wxCustomDataObject') + tools.removeVirtuals(c) + + c.addCppCtor_sip('(const wxString& formatName)', + body='sipCpp = new sipwxCustomDataObject(wxDataFormat(*formatName));') + + # remove the methods having to do with allocating or owning the data buffer + c.find('Alloc').ignore() + c.find('Free').ignore() + c.find('TakeData').ignore() + + c.find('GetData').ignore() + c.addCppMethod('PyObject*', 'GetData', '()', isConst=True, + doc="Returns a reference to the data buffer.", + body="return wxPyMakeBuffer(self->GetData(), self->GetSize());") + + c.find('SetData').ignore() + c.addCppMethod('bool', 'SetData', '(wxPyBuffer* buf)', + cppSignature='bool (size_t len, const void* buf)', + isVirtual=True, + doc="Copies data from the provided buffer to this data object's buffer", + body="return self->SetData(buf->m_len, buf->m_ptr);") + + + + #------------------------------------------------------------ + c = module.find('wxDataObjectComposite') + + c.find('Add.dataObject').transfer = True + + addGetAllFormats(c) + + # The pure virtuals from wxDataObject have implementations here + c.addItem(etgtools.WigCode(code="""\ + virtual size_t GetFormatCount(Direction dir = Get) const; + virtual wxDataFormat GetPreferredFormat(Direction dir = Get) const; + private: + virtual size_t GetDataSize(const wxDataFormat& format) const; + virtual bool GetDataHere(const wxDataFormat& format, void* buf) const; + virtual bool SetData(const wxDataFormat& format, size_t len, const void* buf); + """)) + + + + #------------------------------------------------------------ + c = module.find('wxTextDataObject') + addGetAllFormats(c) + + + #------------------------------------------------------------ + module.addPyCode("PyDataObjectSimple = wx.deprecated(DataObjectSimple)") + module.addPyCode("PyTextDataObject = wx.deprecated(TextDataObject)") + module.addPyCode("PyBitmapDataObject = wx.deprecated(BitmapDataObject)") #----------------------------------------------------------------- tools.doCommonTweaks(module) diff --git a/unittests/test_dataobj.py b/unittests/test_dataobj.py index 9864dcf3..3a3e438a 100644 --- a/unittests/test_dataobj.py +++ b/unittests/test_dataobj.py @@ -1,6 +1,11 @@ import imp_unittest, unittest import wtc import wx +import os + +pngFile = os.path.join(os.path.dirname(__file__), 'toucan.png') + +#print os.getpid(); raw_input('Press enter...') #--------------------------------------------------------------------------- @@ -10,6 +15,9 @@ class DataObjTests(wtc.WidgetTestCase): fmt1 = wx.DataFormat('my custom format') fmt2 = wx.DataFormat(wx.DF_TEXT) self.assertTrue(fmt1 != fmt2) + fmt3 = wx.DataFormat(wx.DF_TEXT) + self.assertTrue(fmt2 == fmt3) + def test_DataFormatIDsExist(self): @@ -34,6 +42,178 @@ class DataObjTests(wtc.WidgetTestCase): wx.DF_HTML wx.DF_MAX + + + def test_DataObjectGetAllFormats(self): + class MyDataObject(wx.DataObject): + def __init__(self): + wx.DataObject.__init__(self) + self.myFormats = [wx.DataFormat(wx.DF_TEXT), + wx.DataFormat(wx.DF_UNICODETEXT)] + + def GetAllFormats(self, d): + return self.myFormats + + def GetFormatCount(self, d): + return len(self.myFormats) + + data = MyDataObject() + if hasattr(data, '_testGetAllFormats'): + data._testGetAllFormats() + + + + def test_DataObject(self): + class MyDataObject(wx.DataObject): + def __init__(self, value=''): + wx.DataObject.__init__(self) + self.myFormats = [wx.DataFormat(wx.DF_TEXT)] + self.myData = bytes(value) + + def GetAllFormats(self, d): + return self.myFormats + def GetFormatCount(self, d): + return len(self.myFormats) + def GetPreferredFormat(self, d): + return self.myFormats[0] + def GetDataSize(self, format): + return len(self.myData) + + def GetDataHere(self, format, buf): + # copy our local data value to buf + assert isinstance(buf, memoryview) + buf[:] = self.myData + return True + + def SetData(self, format, buf): + # copy from buf to our local data value + assert isinstance(buf, memoryview) + self.myData = buf.tobytes() + return True + + # copy + data1 = MyDataObject('This is some data.') + if wx.TheClipboard.Open(): + wx.TheClipboard.SetData(data1) + wx.TheClipboard.Close() + + # paste + data2 = MyDataObject() + if wx.TheClipboard.Open(): + wx.TheClipboard.GetData(data2) + wx.TheClipboard.Close() + + self.assertEqual(data1.myData, data2.myData) + + + + def test_DataObjectSimple1(self): + df = wx.DataFormat(wx.DF_TEXT) + dobj = wx.DataObjectSimple(df) + self.assertTrue(dobj.GetFormatCount() == 1) + self.assertTrue(dobj.GetFormat() == df) + self.assertTrue(dobj.GetAllFormats()[0] == df) + + + def test_DataObjectSimple2(self): + class MyDataObject(wx.DataObjectSimple): + def __init__(self, value=''): + wx.DataObjectSimple.__init__(self) + self.SetFormat(wx.DataFormat(wx.DF_TEXT)) + self.myData = bytes(value) + + def GetDataSize(self): + return len(self.myData) + + def GetDataHere(self, buf): + # copy our local data value to buf + assert isinstance(buf, memoryview) + buf[:] = self.myData + return True + + def SetData(self, buf): + # copy from buf to our local data value + assert isinstance(buf, memoryview) + self.myData = buf.tobytes() + return True + + # copy + data1 = MyDataObject('This is some data.') + if wx.TheClipboard.Open(): + wx.TheClipboard.SetData(data1) + wx.TheClipboard.Close() + + # paste + data2 = MyDataObject() + if wx.TheClipboard.Open(): + wx.TheClipboard.GetData(data2) + wx.TheClipboard.Close() + + self.assertEqual(data1.myData, data2.myData) + + + + def test_CustomDataObject(self): + import pickle + data1 = range(10) + obj = wx.CustomDataObject('my custom format') + obj.SetData(pickle.dumps(data1)) + data2 = pickle.loads(obj.GetData().tobytes()) + self.assertTrue(data1 == data2) + + + + def test_DataObjectComposite(self): + do = wx.DataObjectComposite() + df1 = wx.DataFormat("data type 1") + df2 = wx.DataFormat("data type 2") + d1 = wx.CustomDataObject(df1) + d2 = wx.CustomDataObject(df2) + do.Add(d1, True) + do.Add(d2) + + self.assertTrue(do.GetPreferredFormat() == df1) + d3 = do.GetObject(df2) + self.assertTrue(isinstance(d3, wx.CustomDataObject)) + self.assertTrue(d3 is d2) + + + def test_BitmapDataObject(self): + do = wx.BitmapDataObject() + do.Bitmap = wx.Bitmap(pngFile) + self.assertTrue(do.GetBitmap().IsOk()) + self.assertTrue(do.Bitmap.IsOk()) + + + def test_TextDataObject(self): + data = "This is some data" + do = wx.TextDataObject(data) + self.assertEqual(do.GetText(), data) + self.assertEqual(do.Text, data) + self.assertAlmostEqual(do.GetTextLength(), len(data), delta=1) + self.assertAlmostEqual(do.TextLength, len(data), delta=1) + + + def test_URLDataObject(self): + url = 'http://wxPython.org/' + do = wx.URLDataObject() + do.URL = url + self.assertEqual(do.GetURL(), url) + self.assertEqual(do.URL, url) + + + def test_FileDataObject(self): + do = wx.FileDataObject() + do.AddFile('filename1') + do.AddFile('filename2') + do.AddFile('filename3') + + names = do.GetFilenames() + self.assertTrue(len(names) == 3) + self.assertTrue(names[0] == 'filename1') + self.assertTrue(names == do.Filenames) + + #---------------------------------------------------------------------------