From 95c5c5f50e7d8a55dfbfcddd7090dbaf3a571fce Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Thu, 29 Mar 2012 16:59:21 +0000 Subject: [PATCH] Add treectrl git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71046 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- TODO.txt | 3 +- etg/_core.py | 1 + etg/treectrl.py | 237 +++++++++++++++++++++++++++++++++++++ src/treeitemdata.h | 48 ++++++++ src/wxpy_api.h | 3 + unittests/test_treectrl.py | 141 ++++++++++++++++++++++ 6 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 etg/treectrl.py create mode 100644 src/treeitemdata.h create mode 100644 unittests/test_treectrl.py diff --git a/TODO.txt b/TODO.txt index 4a214eb4..aefdcf90 100644 --- a/TODO.txt +++ b/TODO.txt @@ -145,10 +145,9 @@ other dev stuff * all missing common dialogs * print (as in print framework classes) * mdi (die mdi! die!) - * treectrl * dirctrl * cshelp - * dragimag + * dragimag * datectrl * hyperlink * pickerbase, clrpicker, filepicker, fontpicker diff --git a/etg/_core.py b/etg/_core.py index c36caad9..64ed4be5 100644 --- a/etg/_core.py +++ b/etg/_core.py @@ -148,6 +148,7 @@ INCLUDES = [ # core 'toolbar', 'infobar', 'listctrl', + 'treectrl', # toplevel and dialogs 'nonownedwnd', diff --git a/etg/treectrl.py b/etg/treectrl.py new file mode 100644 index 00000000..82facc1f --- /dev/null +++ b/etg/treectrl.py @@ -0,0 +1,237 @@ +#--------------------------------------------------------------------------- +# Name: etg/treectrl.py +# Author: Robin Dunn +# +# Created: 26-Mar-2012 +# Copyright: (c) 2012 by Total Control Software +# License: wxWindows License +#--------------------------------------------------------------------------- + +import etgtools +import etgtools.tweaker_tools as tools + +PACKAGE = "wx" +MODULE = "_core" +NAME = "treectrl" # Base name of the file to generate to for this script +DOCSTRING = "" + +# The classes and/or the basename of the Doxygen XML files to be processed by +# this script. +ITEMS = [ "wxTreeItemId", + ##"wxTreeItemData", + "wxTreeCtrl", + "wxTreeEvent", + ] + +DEPENDS = [ 'src/treeitemdata.h' ] + +#--------------------------------------------------------------------------- + +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. + + + + #------------------------------------------------------- + c = module.find('wxTreeItemId') + assert isinstance(c, etgtools.ClassDef) + c.addCppMethod('int', '__nonzero__', '()', """\ + return self->IsOk(); + """) + + td = etgtools.TypedefDef(name='wxTreeItemIdValue', type='void*') + module.insertItemBefore(c, td) + + + #------------------------------------------------------- + # Instead of using the wxTreeItemData defined in the dox file we'll + # create our own subclass that knows about dealing with PyObjects + # properly and then tweak the wxTreeCtrl methods below to define the use + # of this new class instead. + + # TODO: build this with ClassDef, etc. extractor objects instead of WigCode. + + # OR maybe just assume that nobody uses the Set/GetId and make it a %MappedType? + # Then Set/GetItemPyData can just be aliases. + tid = etgtools.WigCode("""\ + class TreeItemData { + %TypeHeaderCode + #include "treeitemdata.h" + %End + + public: + TreeItemData(PyObject* obj = NULL); + ~TreeItemData(); + + const wxTreeItemId& GetId() const; + void SetId(const wxTreeItemId& id); + + PyObject* GetData(); + void SetData(PyObject* obj); + + %Property(name=Id, get=GetId, set=SetId) + %Property(name=Data, get=GetData, set=SetData) + }; + """) + module.insertItemAfter(c, tid) + + + + #------------------------------------------------------- + c = module.find('wxTreeCtrl') + tools.fixWindowClass(c) + module.addGlobalStr('wxTreeCtrlNameStr', before=c) + + + # Switch all wxTreeItemData parameters to our TreeItemData class. + for item in c.allItems(): + if hasattr(item, 'type') and item.type == 'wxTreeItemData *': + item.type = 'TreeItemData *' + if isinstance(item, etgtools.ParamDef): + item.transfer = True + + # Typecast the return value to our data item type + c.find('GetItemData').setCppCode( + 'return dynamic_cast(self->GetItemData(*item));') + + # The setter takes ownership of the data object + c.find('SetItemData.data').transfer = True + + # Add methods that allow direct setting/getting of the PyObject without + # requiring the programmer to use our TreeItemData class themselves. + c.addCppMethod('PyObject*', 'GetItemPyData', '(const wxTreeItemId& item)', + doc='Get the Python object associated with the tree item.', + body="""\ + TreeItemData* data = (TreeItemData*)self->GetItemData(*item); + if (data == NULL) { + RETURN_NONE(); + } + return data->GetData(); + """) + c.addCppMethod('void', 'SetItemPyData', '(const wxTreeItemId& item, PyObject* obj)', + doc='Associate a Python object with an item in the treectrl.', + body="""\ + TreeItemData* data = (TreeItemData*)self->GetItemData(*item); + if (data == NULL) { + data = new TreeItemData(obj); + self->SetItemData(*item, data); + } else { + data->SetData(obj); + } + """) + c.addPyCode("""\ + TreeCtrl.GetPyData = wx.deprecated(TreeCtrl.GetItemPyData) + TreeCtrl.SetPyData = wx.deprecated(TreeCtrl.SetItemPyData) + """) + + + # We can't use wxClassInfo + c.find('EditLabel.textCtrlClass').ignore() + + # Replace GetSelections with a method that returns a Python list + # size_t GetSelections(wxArrayTreeItemIds& selection) const; + c.find('GetSelections').ignore() + c.addCppMethod('PyObject*', 'GetSelections', '()', + doc='Returns a list of currently selected items in the tree. This function ' + 'can be called only if the control has the wx.TR_MULTIPLE style.', + body="""\ + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + PyObject* rval = PyList_New(0); + wxArrayTreeItemIds array; + size_t num, x; + num = self->GetSelections(array); + for (x=0; x < num; x++) { + wxTreeItemId *tii = new wxTreeItemId(array.Item(x)); + PyObject* item = wxPyConstructObject((void*)tii, wxT("wxTreeItemId"), true); + PyList_Append(rval, item); + Py_DECREF(item); + } + wxPyEndBlockThreads(blocked); + return rval; + """) + + # Change GetBoundingRect to return the rectangle instead of modifying the parameter. + #bool GetBoundingRect(const wxTreeItemId& item, wxRect& rect, bool textOnly = false) const; + c.find('GetBoundingRect').ignore() + c.addCppMethod('PyObject*', 'GetBoundingRect', '(const wxTreeItemId& item, bool textOnly=false)', + doc="""\ + Returns the rectangle bounding the item. If textOnly is true, + only the rectangle around the item's label will be returned, otherwise + the item's image is also taken into account. The return value may be None + if the rectangle was not successfully retrieved, such as if the item is + currently not visible. + """, + isFactory=True, + body="""\ + wxRect rect; + if (self->GetBoundingRect(*item, rect, textOnly)) { + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + wxRect* r = new wxRect(rect); + PyObject* val = wxPyConstructObject((void*)r, wxT("wxRect"), true); + wxPyEndBlockThreads(blocked); + return val; + } + else + RETURN_NONE(); + """) + + + # switch the virtualness back on for those methods that need to have it. + c.find('OnCompareItems').isVirtual = True + + + # transfer imagelist ownership + c.find('AssignImageList.imageList').transfer = True + c.find('AssignStateImageList.imageList').transfer = True + c.find('AssignButtonsImageList.imageList').transfer = True + + + # Make the cookie values be returned, instead of setting it through the parameter + c.find('GetFirstChild.cookie').out = True + c.find('GetFirstChild.cookie').inOut = True + + + + #------------------------------------------------------- + c = module.find('wxTreeEvent') + tools.fixEventClass(c) + + c.addPyCode("""\ + EVT_TREE_BEGIN_DRAG = PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_DRAG , 1) + EVT_TREE_BEGIN_RDRAG = PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_RDRAG , 1) + EVT_TREE_BEGIN_LABEL_EDIT = PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT , 1) + EVT_TREE_END_LABEL_EDIT = PyEventBinder(wxEVT_COMMAND_TREE_END_LABEL_EDIT , 1) + EVT_TREE_DELETE_ITEM = PyEventBinder(wxEVT_COMMAND_TREE_DELETE_ITEM , 1) + EVT_TREE_GET_INFO = PyEventBinder(wxEVT_COMMAND_TREE_GET_INFO , 1) + EVT_TREE_SET_INFO = PyEventBinder(wxEVT_COMMAND_TREE_SET_INFO , 1) + EVT_TREE_ITEM_EXPANDED = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_EXPANDED , 1) + EVT_TREE_ITEM_EXPANDING = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_EXPANDING , 1) + EVT_TREE_ITEM_COLLAPSED = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_COLLAPSED , 1) + EVT_TREE_ITEM_COLLAPSING = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_COLLAPSING , 1) + EVT_TREE_SEL_CHANGED = PyEventBinder(wxEVT_COMMAND_TREE_SEL_CHANGED , 1) + EVT_TREE_SEL_CHANGING = PyEventBinder(wxEVT_COMMAND_TREE_SEL_CHANGING , 1) + EVT_TREE_KEY_DOWN = PyEventBinder(wxEVT_COMMAND_TREE_KEY_DOWN , 1) + EVT_TREE_ITEM_ACTIVATED = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_ACTIVATED , 1) + EVT_TREE_ITEM_RIGHT_CLICK = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK , 1) + EVT_TREE_ITEM_MIDDLE_CLICK = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK, 1) + EVT_TREE_END_DRAG = PyEventBinder(wxEVT_COMMAND_TREE_END_DRAG , 1) + EVT_TREE_STATE_IMAGE_CLICK = PyEventBinder(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK, 1) + EVT_TREE_ITEM_GETTOOLTIP = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, 1) + EVT_TREE_ITEM_MENU = PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) + """) + + #----------------------------------------------------------------- + tools.doCommonTweaks(module) + tools.runGenerators(module) + + +#--------------------------------------------------------------------------- +if __name__ == '__main__': + run() + diff --git a/src/treeitemdata.h b/src/treeitemdata.h new file mode 100644 index 00000000..7461cb3c --- /dev/null +++ b/src/treeitemdata.h @@ -0,0 +1,48 @@ +//-------------------------------------------------------------------------- +// Name: treeitemdata.h +// Purpose: This class is used to associate a PyObject with a tree item. +// +// Author: Robin Dunn +// +// Created: 26-Mar-2012 +// Copyright: (c) 2012 by Total Control Software +// Licence: wxWindows license +//-------------------------------------------------------------------------- + +#ifndef TREEITEMDATA_H +#define TREEITEMDATA_H + +#include + +// A wxTreeItemData that knows what to do with PyObjects for maintianing the refcount +class TreeItemData : public wxTreeItemData { +public: + TreeItemData(PyObject* obj = NULL) { + if (obj == NULL) + obj = Py_None; + wxPyBLOCK_THREADS( Py_INCREF(obj) ); + m_obj = obj; + } + + ~TreeItemData() { + wxPyBLOCK_THREADS( Py_DECREF(m_obj) ); + } + + PyObject* GetData() { + wxPyBLOCK_THREADS( Py_INCREF(m_obj) ); + return m_obj; + } + + void SetData(PyObject* obj) { + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + Py_DECREF(m_obj); + m_obj = obj; + Py_INCREF(obj); + wxPyEndBlockThreads(blocked); + } + +private: + PyObject* m_obj; +}; + +#endif diff --git a/src/wxpy_api.h b/src/wxpy_api.h index 865b9c83..73469443 100644 --- a/src/wxpy_api.h +++ b/src/wxpy_api.h @@ -88,6 +88,9 @@ inline void wxPyEndAllowThreads(PyThreadState* saved) { wxPyBLOCK_THREADS( PyErr_SetString(PyExc_NotImplementedError, msg) ); +// A convenience macro for properly returning Py_None +//#define RETURN_NONE() { Py_INCREF(Py_None); return Py_None; } +#define RETURN_NONE() { wxPyBLOCK_THREADS(Py_INCREF(Py_None)); return Py_None; } //-------------------------------------------------------------------------- // The API items whose implementation can not or should not be inline diff --git a/unittests/test_treectrl.py b/unittests/test_treectrl.py new file mode 100644 index 00000000..e3e18f34 --- /dev/null +++ b/unittests/test_treectrl.py @@ -0,0 +1,141 @@ +import imp_unittest, unittest +import wtc +import wx + +#--------------------------------------------------------------------------- + +class treectrl_Tests(wtc.WidgetTestCase): + + def test_treectrlCtor(self): + t = wx.TreeCtrl(self.frame) + + def test_treectrlDefaultCtor(self): + t = wx.TreeCtrl() + t.Create(self.frame) + + + def test_treectrlTreeItemId(self): + tree = wx.TreeCtrl(self.frame) + root = tree.AddRoot('root item') + self.assertTrue( isinstance(root, wx.TreeItemId) ) + self.assertTrue( root.IsOk() ) + + r = tree.GetRootItem() + self.assertTrue( r is not root ) + self.assertTrue( r == root ) + + child = tree.AppendItem(root, 'child item') + self.assertTrue( child is not root ) + self.assertTrue( child != root ) + + + def test_treectrlTreeItemData(self): + value = 'Some Python Object' + tree = wx.TreeCtrl(self.frame) + data = wx.TreeItemData(value) + root = tree.AddRoot('root item', data=data) + d = tree.GetItemData(root) + self.assertTrue(d.GetData() == value) + self.assertTrue(d.Data == value) + self.assertTrue(d.Id == root) + tree.SetItemData(root, None) + self.assertTrue(tree.GetItemData(root) is None) + + + def test_treectrlTreeItemPyData(self): + value = 'Some Python Object' + tree = wx.TreeCtrl(self.frame) + root = tree.AddRoot('root item') + tree.SetItemPyData(root, value) + self.assertTrue(tree.GetItemPyData(root) == value) + self.assertTrue(tree.GetItemData(root).GetData() == value) + tree.SetItemPyData(root, None) + self.assertTrue(tree.GetItemPyData(root) is None) + + + + def test_treectrlConstantsExist(self): + wx.TR_NO_BUTTONS + wx.TR_HAS_BUTTONS + wx.TR_NO_LINES + wx.TR_LINES_AT_ROOT + wx.TR_SINGLE + wx.TR_MULTIPLE + wx.TR_HAS_VARIABLE_ROW_HEIGHT + wx.TR_EDIT_LABELS + wx.TR_HIDE_ROOT + wx.TR_ROW_LINES + wx.TR_FULL_ROW_HIGHLIGHT + wx.TR_DEFAULT_STYLE + wx.TR_TWIST_BUTTONS + wx.TreeItemIcon_Normal + wx.TreeItemIcon_Selected + wx.TreeItemIcon_Expanded + wx.TreeItemIcon_SelectedExpanded + wx.TREE_ITEMSTATE_NONE + wx.TREE_ITEMSTATE_NEXT + wx.TREE_ITEMSTATE_PREV + wx.TREE_HITTEST_ABOVE + wx.TREE_HITTEST_BELOW + wx.TREE_HITTEST_NOWHERE + wx.TREE_HITTEST_ONITEMBUTTON + wx.TREE_HITTEST_ONITEMICON + wx.TREE_HITTEST_ONITEMINDENT + wx.TREE_HITTEST_ONITEMLABEL + wx.TREE_HITTEST_ONITEMRIGHT + wx.TREE_HITTEST_ONITEMSTATEICON + wx.TREE_HITTEST_TOLEFT + wx.TREE_HITTEST_TORIGHT + wx.TREE_HITTEST_ONITEMUPPERPART + wx.TREE_HITTEST_ONITEMLOWERPART + wx.TREE_HITTEST_ONITEM + + def test_treeEventsExist(self): + wx.wxEVT_COMMAND_TREE_BEGIN_DRAG + wx.wxEVT_COMMAND_TREE_BEGIN_RDRAG + wx.wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT + wx.wxEVT_COMMAND_TREE_END_LABEL_EDIT + wx.wxEVT_COMMAND_TREE_DELETE_ITEM + wx.wxEVT_COMMAND_TREE_GET_INFO + wx.wxEVT_COMMAND_TREE_SET_INFO + wx.wxEVT_COMMAND_TREE_ITEM_EXPANDED + wx.wxEVT_COMMAND_TREE_ITEM_EXPANDING + wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED + wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSING + wx.wxEVT_COMMAND_TREE_SEL_CHANGED + wx.wxEVT_COMMAND_TREE_SEL_CHANGING + wx.wxEVT_COMMAND_TREE_KEY_DOWN + wx.wxEVT_COMMAND_TREE_ITEM_ACTIVATED + wx.wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK + wx.wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK + wx.wxEVT_COMMAND_TREE_END_DRAG + wx.wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK + wx.wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP + wx.wxEVT_COMMAND_TREE_ITEM_MENU + + wx.EVT_TREE_BEGIN_DRAG + wx.EVT_TREE_BEGIN_RDRAG + wx.EVT_TREE_BEGIN_LABEL_EDIT + wx.EVT_TREE_END_LABEL_EDIT + wx.EVT_TREE_DELETE_ITEM + wx.EVT_TREE_GET_INFO + wx.EVT_TREE_SET_INFO + wx.EVT_TREE_ITEM_EXPANDED + wx.EVT_TREE_ITEM_EXPANDING + wx.EVT_TREE_ITEM_COLLAPSED + wx.EVT_TREE_ITEM_COLLAPSING + wx.EVT_TREE_SEL_CHANGED + wx.EVT_TREE_SEL_CHANGING + wx.EVT_TREE_KEY_DOWN + wx.EVT_TREE_ITEM_ACTIVATED + wx.EVT_TREE_ITEM_RIGHT_CLICK + wx.EVT_TREE_ITEM_MIDDLE_CLICK + wx.EVT_TREE_END_DRAG + wx.EVT_TREE_STATE_IMAGE_CLICK + wx.EVT_TREE_ITEM_GETTOOLTIP + wx.EVT_TREE_ITEM_MENU + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main()