diff --git a/etg/_dataview.py b/etg/_dataview.py index 2d4868b6..7a37c2f8 100644 --- a/etg/_dataview.py +++ b/etg/_dataview.py @@ -27,11 +27,9 @@ ITEMS = [ ] # easier to promote one of these to module status later if desired, simply # remove it from this list of Includes, and change the MODULE value in the # promoted script to be the same as its NAME. - -INCLUDES = [ - 'dataview', - 'dataviewhelpers', - ] +INCLUDES = [ 'dvcvariant', + 'dataview', + ] # Separate the list into those that are generated from ETG scripts and the @@ -46,7 +44,7 @@ OTHERDEPS = [] def run(): # Parse the XML file(s) building a collection of Extractor objects - module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING) + module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING, check4unittest=False) etgtools.parseDoxyXML(module, ITEMS) #----------------------------------------------------------------- @@ -55,15 +53,10 @@ def run(): module.addHeaderCode('#include ') module.addImport('_core') + module.addPyCode("import wx", order=10) + module.addInclude(INCLUDES) - # This code is inserted into the module initialization function - module.addPostInitializerCode(""" - wxPyDataViewModuleInject(sipModuleDict); - """) - # Here is the function it calls - module.addCppCode(wxPyDataViewModuleInject) - #----------------------------------------------------------------- tools.doCommonTweaks(module) @@ -73,12 +66,6 @@ def run(): #--------------------------------------------------------------------------- -wxPyDataViewModuleInject = """ -void wxPyDataViewModuleInject(PyObject* moduleDict) -{ - -} -""" if __name__ == '__main__': run() diff --git a/etg/dataview.py b/etg/dataview.py index 9ccfef84..6c3d0462 100644 --- a/etg/dataview.py +++ b/etg/dataview.py @@ -3,12 +3,13 @@ # Author: Kevin Ollivier # # Created: 10-Sept-2011 -# Copyright: (c) 2011 by Kevin Ollivier +# Copyright: (c) 2012 by Kevin Ollivier, Robin Dunn # License: wxWindows License #--------------------------------------------------------------------------- import etgtools import etgtools.tweaker_tools as tools +from etgtools import PyFunctionDef, PyCodeDef, PyPropertyDef PACKAGE = "wx" MODULE = "_dataview" @@ -18,45 +19,418 @@ DOCSTRING = "" # The classes and/or the basename of the Doxygen XML files to be processed by # this script. ITEMS = [ + 'wxDataViewItem', + 'wxDataViewItemAttr', + 'wxDataViewIconText', + 'wxDataViewModelNotifier', + + 'wxDataViewModel', + 'wxDataViewListModel', + 'wxDataViewIndexListModel', + 'wxDataViewVirtualListModel', + + 'wxDataViewRenderer', + 'wxDataViewCustomRenderer', + 'wxDataViewTextRenderer', + 'wxDataViewIconTextRenderer', + 'wxDataViewProgressRenderer', + 'wxDataViewSpinRenderer', + 'wxDataViewToggleRenderer', + #'wxDataViewChoiceRenderer', only available in generic dvc + #'wxDataViewChoiceByIndexRenderer', ditto + 'wxDataViewDateRenderer', + 'wxDataViewBitmapRenderer', + 'wxDataViewColumn', 'wxDataViewCtrl', 'wxDataViewEvent', - 'wxDataViewItem', - 'wxDataViewItemAttr', - 'wxDataViewModel', - 'wxDataViewModelNotifier', - 'wxDataViewRenderer', + + 'wxDataViewListCtrl', + 'wxDataViewListStore', + + 'wxDataViewTreeCtrl', + 'wxDataViewTreeStore', ] + #--------------------------------------------------------------------------- def run(): # Parse the XML file(s) building a collection of Extractor objects module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING) - #module.items.append(etgtools.TypedefDef(type='void*', name='wxPyLongPtr')) etgtools.parseDoxyXML(module, ITEMS) #----------------------------------------------------------------- # Tweak the parsed meta objects in the module object as needed for # customizing the generated code and docstrings. - + + + module.addHeaderCode("#include ") + + for item in module.allItems(): + if hasattr(item, 'type') and 'wxVariant' in item.type: + item.type = item.type.replace('wxVariant', 'wxDVCVariant') + + + #----------------------------------------------------------------- + c = module.find('wxDataViewItem') + assert isinstance(c, etgtools.ClassDef) + c.addCppMethod('int', '__nonzero__', '()', """\ + return self->IsOk(); + """) + c.addCppMethod('long', '__hash__', '()', """\ + return (long)self->GetID(); + """) + c.addCppMethod('int', '__cmp__', '(wxDataViewItem* other)', """\ + if ( self->GetID() < other->GetID() ) return -1; + if ( self->GetID() > other->GetID() ) return 1; + return 0; + """) + c.addAutoProperties() + + module.addItem( + tools.wxArrayWrapperTemplate('wxDataViewItemArray', 'wxDataViewItem', module)) + module.addPyCode("NullDataViewItem = DataViewItem()") + + + #----------------------------------------------------------------- c = module.find('wxDataViewModel') - c.abstract = True - # Ignore the stock GetValue API, we handle it in src/dataviewhelpers.sip - c.find('GetValue').ignore() + c.addAutoProperties() - c.addDtor() + c.find('~wxDataViewModel').ignore(False) + + c.find('AddNotifier.notifier').transfer = True + c.find('RemoveNotifier.notifier').transferBack = True + + # Change the GetValue method to return the value instead of passing it + # through a parameter for modification. + c.find('GetValue.variant').out = True - c = module.find('wxDataViewRenderer') + + # The DataViewItemObjectMapper class helps map from data items to Python + # objects, and is used as a base class of PyDataViewModel as a + # convenience. + module.addPyClass('DataViewItemObjectMapper', ['object'], + doc="""\ + This class provides a mechanism for mapping between Python objects and the + DataViewItem objects used by the DataViewModel for tracking the items in + the view. The ID used for the item is the id() of the Python object. Use + `ObjectToItem` to create a DataViewItem using a Python object as its ID, + and use `ItemToObject` to fetch that Python object again later for a given + DataViewItem. + + By default a regular dictionary is used to implement the ID to object + mapping. Optionally a WeakValueDictionary can be useful when there will be + a high turnover of objects and mantaining an extra reference to the + objects would be unwise. If weak references are used then the objects + associated with data items must be weak-referenceable. (Things like + stock lists and dictionaries are not.) See `UseWeakRefs`. + + This class is used in `PyDataViewModel` as a mixin for convenience. + """, + items=[ + PyFunctionDef('__init__', '(self)', + body="""\ + self.mapper = dict() + self.usingWeakRefs = False + """), + + PyFunctionDef('ObjectToItem', '(self, obj)', + doc="Create a DataViewItem for the object, and remember the ID-->obj mapping.", + body="""\ + oid = id(obj) + self.mapper[oid] = obj + return DataViewItem(oid) + """), + + PyFunctionDef('ItemToObject', '(self, item)', + doc="Retrieve the object that was used to create an item.", + body="""\ + oid = int(item.GetID()) + return self.mapper[oid] + """), + + PyFunctionDef('UseWeakRefs', '(self, flag)', + doc="""\ + Switch to or from using a weak value dictionary for keeping the ID to + object map.""", + body="""\ + if flag == self.usingWeakRefs: + return + if flag: + import weakref + newmap = weakref.WeakValueDictionary() + else: + newmap = dict() + newmap.update(self.mapper) + self.mapper = newmap + self.usingWeakRefs = flag + """), + ]) + + module.addPyClass('PyDataViewModel', ['DataViewModel', 'DataViewItemObjectMapper'], + doc="A convenience class that is a DataViewModel combined with an object mapper.", + items=[ + PyFunctionDef('__init__', '(self)', + body="""\ + DataViewModel.__init__(self) + DataViewItemObjectMapper.__init__(self) + """) + ]) + + + #----------------------------------------------------------------- + c = module.find('wxDataViewListModel') + c.addAutoProperties() + + # Change the GetValueByRow method to return the value instead of passing + # it through a parameter for modification. + c.find('GetValueByRow.variant').out = True + + # declare implementations for base class virtuals + c.addItem(etgtools.WigCode("""\ + virtual wxDataViewItem GetParent( const wxDataViewItem &item ) const; + virtual bool IsContainer( const wxDataViewItem &item ) const; + virtual void GetValue( wxDVCVariant &variant /Out/, const wxDataViewItem &item, unsigned int col ) const [void ( wxDVCVariant &variant, const wxDataViewItem &item, unsigned int col )]; + virtual bool SetValue( const wxDVCVariant &variant, const wxDataViewItem &item, unsigned int col ); + virtual bool GetAttr(const wxDataViewItem &item, unsigned int col, wxDataViewItemAttr &attr) const; + virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const; + virtual bool IsListModel() const; + """)) + + + # Add some of the pure virtuals since there are undocumented + # implementations of them in these classes. The others will need to be + # implemented in Python classes derived from these. + for name in ['wxDataViewIndexListModel', 'wxDataViewVirtualListModel']: + c = module.find(name) + + c.addItem(etgtools.WigCode("""\ + virtual unsigned int GetRow(const wxDataViewItem& item) const; + virtual unsigned int GetCount() const; + virtual unsigned int GetChildren( const wxDataViewItem &item, wxDataViewItemArray &children ) const; + """)) + + + # compatibility aliases + module.addPyCode("""\ + PyDataViewIndexListModel = wx.deprecated(DataViewIndexListModel) + PyDataViewVirtualListModel = wx.deprecated(DataViewVirtualListModel) + """) + + + #----------------------------------------------------------------- + + def _fixupBoolGetters(method, sig): + method.type = 'void' + method.find('value').out = True + method.cppSignature = sig + + + c = module.find('wxDataViewRenderer') + c.addPrivateCopyCtor() c.abstract = True + c.addAutoProperties() + c.find('GetView').ignore(False) + + # Change variant getters to return the value + for name, sig in [ + ('GetValue', 'bool (wxDVCVariant& value)'), + ('GetValueFromEditorCtrl', 'bool (wxWindow * editor, wxDVCVariant& value)'), + ]: + _fixupBoolGetters(c.find(name), sig) + + + # Add the pure virtuals since there are undocumented implementations of + # them in all these classes + for name in [ 'wxDataViewTextRenderer', + 'wxDataViewIconTextRenderer', + 'wxDataViewProgressRenderer', + 'wxDataViewSpinRenderer', + 'wxDataViewToggleRenderer', + #'wxDataViewChoiceRenderer', + #'wxDataViewChoiceByIndexRenderer', + 'wxDataViewDateRenderer', + 'wxDataViewBitmapRenderer', + ]: + c = module.find(name) + c.addAutoProperties() + + c.addItem(etgtools.WigCode("""\ + virtual bool SetValue( const wxDVCVariant &value ); + virtual void GetValue( wxDVCVariant &value /Out/ ) const [bool (wxDVCVariant& value)]; + %Property(name=Value, get=GetValue, set=SetValue) + """)) + + + c = module.find('wxDataViewCustomRenderer') + _fixupBoolGetters(c.find('GetValueFromEditorCtrl'), + 'bool (wxWindow * editor, wxDVCVariant& value)') + c.find('GetTextExtent').ignore(False) + + module.addPyCode("""\ + PyDataViewCustomRenderer = wx.deprecated(DataViewCustomRenderer, + "Use DataViewCustomRenderer instead")""") + + # The SpinRenderer has a few more pure virtuals that need to be declared + # since it derives from DataViewCustomRenderer + c = module.find('wxDataViewSpinRenderer') + c.addItem(etgtools.WigCode("""\ + virtual wxSize GetSize() const; + virtual bool Render(wxRect cell, wxDC* dc, int state); + """)) + + + #----------------------------------------------------------------- + c = module.find('wxDataViewColumn') + for m in c.find('wxDataViewColumn').all(): + m.find('renderer').transfer = True + + # declare the virtuals from wxSettableHeaderColumn + c.addItem(etgtools.WigCode("""\ + virtual void SetTitle(const wxString& title); + virtual wxString GetTitle() const; + virtual void SetBitmap(const wxBitmap& bitmap); + virtual wxBitmap GetBitmap() const; + virtual void SetWidth(int width); + virtual int GetWidth() const; + virtual void SetMinWidth(int minWidth); + virtual int GetMinWidth() const; + virtual void SetAlignment(wxAlignment align); + virtual wxAlignment GetAlignment() const; + virtual void SetFlags(int flags); + virtual int GetFlags() const; + virtual bool IsSortKey() const; + virtual void SetSortOrder(bool ascending); + virtual bool IsSortOrderAscending() const; + """)) + + c.addAutoProperties() + c.addProperty('Title', 'GetTitle', 'SetTitle') + c.addProperty('Bitmap', 'GetBitmap', 'SetBitmap') + c.addProperty('Width', 'GetWidth', 'SetWidth') + c.addProperty('MinWidth', 'GetMinWidth', 'SetMinWidth') + c.addProperty('Alignment', 'GetAlignment', 'SetAlignment') + c.addProperty('Flags', 'GetFlags', 'SetFlags') + c.addProperty('SortOrder', 'IsSortOrderAscending', 'SetSortOrder') + + + + #----------------------------------------------------------------- c = module.find('wxDataViewCtrl') + tools.fixWindowClass(c) + module.addGlobalStr('wxDataViewCtrlNameStr', c) + + c.find('AssociateModel.model').transfer = True + c.find('AssociateModel').pyName = '_AssociateModel' + c.addPyMethod('AssociateModel', '(self, model)', + doc="""\ + Associates a DataViewModel with the control. + Ownership of the model object is passed to C++, however it + is reference counted so it can be shared with other views. + """, + body="""\ + import wx.siplib + wasPyOwned = wx.siplib.ispyowned(model) + self._AssociateModel(model) + # Ownership of the python object has just been transferred to + # C++, so DecRef the C++ instance associated with this python + # reference. + if wasPyOwned: + model.DecRef() + """) + + c.find('PrependColumn.col').transfer = True + c.find('InsertColumn.col').transfer = True + c.find('AppendColumn.col').transfer = True - module.includePyCode('src/dataview_ex.py') + c.addPyMethod('GetColumns', '(self)', + doc="Returns a list of column objects.", + body="return [self.GetColumn(i) for i in range(self.GetColumnCount())]") + c.find('GetSelections').ignore() + c.addCppMethod('wxDataViewItemArray*', 'GetSelections', '()', + isConst=True, factory=True, + doc="Returns a list of the currently selected items.", + body="""\ + wxDataViewItemArray* selections = new wxDataViewItemArray; + self->GetSelections(*selections); + return selections; + """) + + + #----------------------------------------------------------------- + c = module.find('wxDataViewEvent') + tools.fixEventClass(c) + + c.addProperty('EditCancelled', 'IsEditCancelled', 'SetEditCanceled') + + + c.find('GetDataBuffer').ignore() + c.addCppMethod('PyObject*', 'GetDataBuffer', '()', isConst=True, + doc="Gets the data buffer for a drop data transfer", + body="""\ + wxPyThreadBlocker blocker; + return wxPyMakeBuffer(self->GetDataBuffer(), self->GetDataSize(), true); + """) + + module.addPyCode("""\ + EVT_DATAVIEW_SELECTION_CHANGED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, 1) + EVT_DATAVIEW_ITEM_ACTIVATED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, 1) + EVT_DATAVIEW_ITEM_COLLAPSED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED, 1) + EVT_DATAVIEW_ITEM_EXPANDED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDED, 1) + EVT_DATAVIEW_ITEM_COLLAPSING = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING, 1) + EVT_DATAVIEW_ITEM_EXPANDING = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING, 1) + EVT_DATAVIEW_ITEM_START_EDITING = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_START_EDITING, 1) + EVT_DATAVIEW_ITEM_EDITING_STARTED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_STARTED, 1) + EVT_DATAVIEW_ITEM_EDITING_DONE = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, 1) + EVT_DATAVIEW_ITEM_VALUE_CHANGED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED, 1) + EVT_DATAVIEW_ITEM_CONTEXT_MENU = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, 1) + EVT_DATAVIEW_COLUMN_HEADER_CLICK = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_CLICK, 1) + EVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, 1) + EVT_DATAVIEW_COLUMN_SORTED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_COLUMN_SORTED, 1) + EVT_DATAVIEW_COLUMN_REORDERED = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_COLUMN_REORDERED, 1) + EVT_DATAVIEW_ITEM_BEGIN_DRAG = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG, 1) + EVT_DATAVIEW_ITEM_DROP_POSSIBLE = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, 1) + EVT_DATAVIEW_ITEM_DROP = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_ITEM_DROP, 1) + EVT_DATAVIEW_CACHE_HINT = wx.PyEventBinder( wxEVT_COMMAND_DATAVIEW_CACHE_HINT, 1 ) + """) + + #----------------------------------------------------------------- + c = module.find('wxDataViewListCtrl') tools.fixWindowClass(c) - module.addItem(tools.wxArrayWrapperTemplate('wxDataViewItemArray', 'wxDataViewItem', module)) + c.find('GetStore').overloads = [] + + c.find('AppendItem.values').type = 'const wxVariantVector&' + c.find('PrependItem.values').type = 'const wxVariantVector&' + c.find('InsertItem.values').type = 'const wxVariantVector&' + + c.find('GetValue.value').out = True + + for name in 'AppendColumn InsertColumn PrependColumn'.split(): + for m in c.find(name).all(): + m.find('column').transfer = True + + + c = module.find('wxDataViewListStore') + c.find('AppendItem.values').type = 'const wxVariantVector&' + c.find('PrependItem.values').type = 'const wxVariantVector&' + c.find('InsertItem.values').type = 'const wxVariantVector&' + c.find('GetValueByRow.value').out = True + c.addAutoProperties() + + + #----------------------------------------------------------------- + c = module.find('wxDataViewTreeCtrl') + tools.fixWindowClass(c) + c.find('GetStore').overloads = [] + + + c = module.find('wxDataViewTreeStore') + c.addAutoProperties() + #----------------------------------------------------------------- tools.doCommonTweaks(module) diff --git a/etg/headercol.py b/etg/headercol.py index 29b18aab..67753654 100644 --- a/etg/headercol.py +++ b/etg/headercol.py @@ -35,7 +35,7 @@ def run(): c = module.find('wxHeaderColumn') isinstance(c, etgtools.ClassDef) - c.abstract = True + #c.abstract = True c.addAutoProperties() c.instanceCode = 'sipCpp = new wxHeaderColumnSimple("");' @@ -52,7 +52,7 @@ def run(): c = module.find('wxSettableHeaderColumn') - c.abstract = True + #c.abstract = True c.addAutoProperties() # This class adds some setters to go with the getters (and IsFoo) methods diff --git a/samples/dataview/CustomRenderer.py b/samples/dataview/CustomRenderer.py new file mode 100644 index 00000000..57c92976 --- /dev/null +++ b/samples/dataview/CustomRenderer.py @@ -0,0 +1,167 @@ +import sys +import wx +import wx.dataview as dv + +#import os; print 'PID:', os.getpid(); raw_input("Press enter...") + +#---------------------------------------------------------------------- + +class MyCustomRenderer(dv.DataViewCustomRenderer): + def __init__(self, log, *args, **kw): + dv.DataViewCustomRenderer.__init__(self, *args, **kw) + self.log = log + self.value = None + + def SetValue(self, value): + #self.log.write('SetValue: %s' % value) + self.value = value + return True + + def GetValue(self): + #self.log.write('GetValue') + return self.value + + def GetSize(self): + # Return the size needed to display the value. The renderer + # has a helper function we can use for measuring text that is + # aware of any custom attributes that may have been set for + # this item. + size = self.GetTextExtent(self.value) + return size + + + def Render(self, rect, dc, state): + if state != 0: + self.log.write('Render: %s, %d\n' % (rect, state)) + + if not state & dv.DATAVIEW_CELL_SELECTED: + # we'll draw a shaded background to see if the rect correctly + # fills the cell + dc.SetBrush(wx.Brush('light grey')) + dc.SetPen(wx.TRANSPARENT_PEN) + rect.Deflate(1, 1) + dc.DrawRoundedRectangle(rect, 2) + + # And then finish up with this helper function that draws the + # text for us, dealing with alignment, font and color + # attributes, etc + self.RenderText(self.value, + 4, # x-offset, to compensate for the rounded rectangles + rect, + dc, + state # wxDataViewCellRenderState flags + ) + return True + + + # The HasEditorCtrl, CreateEditorCtrl and GetValueFromEditorCtrl + # methods need to be implemented if this renderer is going to + # support in-place editing of the cell value, otherwise they can + # be omitted. + + def HasEditorCtrl(self): + self.log.write('HasEditorCtrl') + return True + + + def CreateEditorCtrl(self, parent, labelRect, value): + self.log.write('CreateEditorCtrl: %s' % labelRect) + ctrl = wx.TextCtrl(parent, + value=value, + pos=labelRect.Position, + size=labelRect.Size) + + # select the text and put the caret at the end + ctrl.SetInsertionPointEnd() + ctrl.SelectAll() + + return ctrl + + + def GetValueFromEditorCtrl(self, editor): + self.log.write('GetValueFromEditorCtrl: %s' % editor) + value = editor.GetValue() + return value + + + # The LeftClick and Activate methods serve as notifications + # letting you know that the user has either clicked or + # double-clicked on an item. Implementing them in your renderer + # is optional. + + def LeftClick(self, pos, cellRect, model, item, col): + self.log.write('LeftClick') + return False + + + def Activate(self, cellRect, model, item, col): + self.log.write('Activate') + return False + + +#---------------------------------------------------------------------- + +# To help focus this sample on the custom renderer, we'll reuse the +# model class from another sample. +from IndexListModel import TestModel + + + +class TestPanel(wx.Panel): + def __init__(self, parent, log, model=None, data=None): + self.log = log + wx.Panel.__init__(self, parent, -1) + + # Create a dataview control + self.dvc = dv.DataViewCtrl(self, style=wx.BORDER_THEME + | dv.DV_ROW_LINES + #| dv.DV_HORIZ_RULES + | dv.DV_VERT_RULES + | dv.DV_MULTIPLE + ) + + # Create an instance of the model + if model is None: + self.model = TestModel(data, log) + else: + self.model = model + self.dvc.AssociateModel(self.model) + + # Now we create some columns. + c0 = self.dvc.AppendTextColumn("Id", 0, width=40) + c0.Alignment = wx.ALIGN_RIGHT + c0.MinWidth = 40 + + # We'll use our custom renderer for these columns + for title, col, width in [ ('Artist', 1, 170), + ('Title', 2, 260), + ('Genre', 3, 80)]: + renderer = MyCustomRenderer(self.log, mode=dv.DATAVIEW_CELL_EDITABLE) + column = dv.DataViewColumn(title, renderer, col, width=width) + column.Alignment = wx.ALIGN_LEFT + self.dvc.AppendColumn(column) + + # Layout + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.dvc, 1, wx.EXPAND) + + + +#---------------------------------------------------------------------- + +def main(): + from data import musicdata + + app = wx.App() + frm = wx.Frame(None, title="CustomRenderer sample", size=(700,500)) + pnl = TestPanel(frm, sys.stdout, data=musicdata) + frm.Show() + app.MainLoop() + + + + +if __name__ == '__main__': + main() + +#---------------------------------------------------------------------- diff --git a/samples/dataview/DataViewModel.py b/samples/dataview/DataViewModel.py new file mode 100644 index 00000000..a94cf438 --- /dev/null +++ b/samples/dataview/DataViewModel.py @@ -0,0 +1,307 @@ +import sys +import wx +import wx.dataview as dv + +import random + +#---------------------------------------------------------------------- + +def makeBlank(self): + # Just a little helper function to make an empty image for our + # model to use. + empty = wx.EmptyBitmap(16,16,32) + dc = wx.MemoryDC(empty) + dc.SetBackground(wx.Brush((0,0,0,0))) + dc.Clear() + del dc + return empty + +#---------------------------------------------------------------------- +# We'll use instaces of these classes to hold our music data. Items in the +# tree will get associated back to the coresponding Song or Genre object. + +class Song(object): + def __init__(self, id, artist, title, genre): + self.id = id + self.artist = artist + self.title = title + self.genre = genre + self.like = False + # get a random date value + d = random.choice(range(27))+1 + m = random.choice(range(12)) + y = random.choice(range(1980, 2005)) + self.date = wx.DateTime.FromDMY(d,m,y) + + def __repr__(self): + return 'Song: %s-%s' % (self.artist, self.title) + + +class Genre(object): + def __init__(self, name): + self.name = name + self.songs = [] + + def __repr__(self): + return 'Genre: ' + self.name + +#---------------------------------------------------------------------- + +# This model acts as a bridge between the DataViewCtrl and the music data, and +# organizes it hierarchically as a collection of Genres, each of which is a +# collection of songs. + +# This model provides these data columns: +# +# 0. Genre: string +# 1. Artist: string +# 2. Title: string +# 3. id: integer +# 4. Aquired: date +# 5. Liked: bool +# + +class MyTreeListModel(dv.PyDataViewModel): + def __init__(self, data, log): + dv.PyDataViewModel.__init__(self) + self.data = data + self.log = log + + # The PyDataViewModel derives from both DataViewModel and from + # DataViewItemObjectMapper, which has methods that help associate + # data view items with Python objects. Normally a dictionary is used + # so any Python object can be used as data nodes. If the data nodes + # are weak-referencable then the objmapper can use a + # WeakValueDictionary instead. + self.UseWeakRefs(True) + + + # Report how many columns this model provides data for. + def GetColumnCount(self): + return 6 + + + def GetChildren(self, parent, children): + # The view calls this method to find the children of any node in the + # control. There is an implicit hidden root node, and the top level + # item(s) should be reported as children of this node. A List view + # simply provides all items as children of this hidden root. A Tree + # view adds additional items as children of the other items, as needed, + # to provide the tree hierachy. + + # If the parent item is invalid then it represents the hidden root + # item, so we'll use the genre objects as its children and they will + # end up being the collection of visible roots in our tree. + if not parent: + for genre in self.data: + children.append(self.ObjectToItem(genre)) + return len(self.data) + + # Otherwise we'll fetch the python object associated with the parent + # item and make DV items for each of it's child objects. + node = self.ItemToObject(parent) + if isinstance(node, Genre): + for song in node.songs: + children.append(self.ObjectToItem(song)) + return len(node.songs) + return 0 + + + def IsContainer(self, item): + # Return True if the item has children, False otherwise. + + # The hidden root is a container + if not item: + return True + # and in this model the genre objects are containers + node = self.ItemToObject(item) + if isinstance(node, Genre): + return True + # but everything else (the song objects) are not + return False + + + def GetParent(self, item): + # Return the item which is this item's parent. + ##self.log.write("GetParent\n") + + if not item: + return dv.NullDataViewItem + + node = self.ItemToObject(item) + if isinstance(node, Genre): + return dv.NullDataViewItem + elif isinstance(node, Song): + for g in self.data: + if g.name == node.genre: + return self.ObjectToItem(g) + + + def GetValue(self, item, col): + # Return the value to be displayed for this item and column. For this + # example we'll just pull the values from the data objects we + # associated with the items in GetChildren. + + # Fetch the data object for this item. + node = self.ItemToObject(item) + + if isinstance(node, Genre): + # We'll only use the first column for the Genre objects, + # for the other columns lets just return empty values + mapper = { 0 : node.name, + 1 : "", + 2 : "", + 3 : "", + 4 : wx.DateTime.FromTimeT(0), # TODO: There should be some way to indicate a null value... + 5 : False, + } + return mapper[col] + + + elif isinstance(node, Song): + mapper = { 0 : node.genre, + 1 : node.artist, + 2 : node.title, + 3 : node.id, + 4 : node.date, + 5 : node.like, + } + return mapper[col] + + else: + raise RuntimeError("unknown node type") + + + + def GetAttr(self, item, col, attr): + ##self.log.write('GetAttr') + node = self.ItemToObject(item) + if isinstance(node, Genre): + attr.SetColour('blue') + attr.SetBold(True) + return True + return False + + + def SetValue(self, value, item, col): + self.log.write("SetValue: col %d, %s\n" % (col, value)) + + # We're not allowing edits in column zero (see below) so we just need + # to deal with Song objects and cols 1 - 5 + + node = self.ItemToObject(item) + if isinstance(node, Song): + if col == 1: + node.artist = value + elif col == 2: + node.title = value + elif col == 3: + node.id = value + elif col == 4: + node.date = value + elif col == 5: + node.like = value + return True + +#---------------------------------------------------------------------- + +class TestPanel(wx.Panel): + def __init__(self, parent, log, data=None, model=None): + self.log = log + wx.Panel.__init__(self, parent, -1) + + # Create a dataview control + self.dvc = dv.DataViewCtrl(self, + style=wx.BORDER_THEME + | dv.DV_ROW_LINES # nice alternating bg colors + #| dv.DV_HORIZ_RULES + | dv.DV_VERT_RULES + | dv.DV_MULTIPLE + ) + + # Create an instance of our model... + if model is None: + self.model = MyTreeListModel(data, log) + else: + self.model = model + + # Tel the DVC to use the model + self.dvc.AssociateModel(self.model) + + # Define the columns that we want in the view. Notice the + # parameter which tells the view which col in the data model to pull + # values from for each view column. + if 0: + self.tr = tr = dv.DataViewTextRenderer() + c0 = dv.DataViewColumn("Genre", # title + tr, # renderer + 0, # data model column + width=80) + self.dvc.AppendColumn(c0) + else: + self.dvc.AppendTextColumn("Genre", 0, width=80) + + c1 = self.dvc.AppendTextColumn("Artist", 1, width=170, mode=dv.DATAVIEW_CELL_EDITABLE) + c2 = self.dvc.AppendTextColumn("Title", 2, width=260, mode=dv.DATAVIEW_CELL_EDITABLE) + c3 = self.dvc.AppendDateColumn('Acquired', 4, width=100, mode=dv.DATAVIEW_CELL_ACTIVATABLE) + c4 = self.dvc.AppendToggleColumn('Like', 5, width=40, mode=dv.DATAVIEW_CELL_ACTIVATABLE) + + # Notice how we pull the data from col 3, but this is the 6th col + # added to the DVC. The order of the view columns is not dependent on + # the order of the model columns at all. + c5 = self.dvc.AppendTextColumn("id", 3, width=40, mode=dv.DATAVIEW_CELL_EDITABLE) + c5.Alignment = wx.ALIGN_RIGHT + + # Set some additional attributes for all the columns + for c in self.dvc.Columns: + c.Sortable = True + c.Reorderable = True + + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.dvc, 1, wx.EXPAND) + + b1 = wx.Button(self, label="New View", name="newView") + self.Bind(wx.EVT_BUTTON, self.OnNewView, b1) + + self.Sizer.Add(b1, 0, wx.ALL, 5) + + + def OnNewView(self, evt): + f = wx.Frame(None, title="New view, shared model", size=(600,400)) + TestPanel(f, self.log, model=self.model) + b = f.FindWindowByName("newView") + b.Disable() + f.Show() + + +#---------------------------------------------------------------------- + +def main(): + from data import musicdata + + # our data structure will be a collection of Genres, each of which is a + # collection of Songs + data = dict() + for key, artist, title, genre in musicdata: + song = Song(str(key), artist, title, genre) + genre = data.get(song.genre) + if genre is None: + genre = Genre(song.genre) + data[song.genre] = genre + genre.songs.append(song) + data = data.values() + + app = wx.App() + frm = wx.Frame(None, title="DataViewModel sample", size=(700,500)) + pnl = TestPanel(frm, sys.stdout, data=data) + frm.Show() + app.MainLoop() + + +#---------------------------------------------------------------------- + + +if __name__ == '__main__': + main() + diff --git a/samples/dataview/IndexListModel.py b/samples/dataview/IndexListModel.py new file mode 100644 index 00000000..406085e1 --- /dev/null +++ b/samples/dataview/IndexListModel.py @@ -0,0 +1,228 @@ +import sys +import wx +import wx.dataview as dv + +#---------------------------------------------------------------------- + +# This model class provides the data to the view when it is asked for. +# Since it is a list-only model (no hierachical data) then it is able +# to be referenced by row rather than by item object, so in this way +# it is easier to comprehend and use than other model types. In this +# example we also provide a Compare function to assist with sorting of +# items in our model. Notice that the data items in the data model +# object don't ever change position due to a sort or column +# reordering. The view manages all of that and maps view rows and +# columns to the model's rows and columns as needed. +# +# For this example our data is stored in a simple list of lists. In +# real life you can use whatever you want or need to hold your data. + +class TestModel(dv.DataViewIndexListModel): + def __init__(self, data, log): + dv.DataViewIndexListModel.__init__(self, len(data)) + self.data = data + self.log = log + + # This method is called to provide the data object for a + # particular row,col + def GetValueByRow(self, row, col): + return str(self.data[row][col]) + + # This method is called when the user edits a data item in the view. + def SetValueByRow(self, value, row, col): + self.log.write("SetValue: (%d,%d) %s\n" % (row, col, value)) + self.data[row][col] = value + return True + + # Report how many columns this model provides data for. + def GetColumnCount(self): + return len(self.data[0]) + + # Report the number of rows in the model + def GetCount(self): + #self.log.write('GetCount') + return len(self.data) + + # Called to check if non-standard attributes should be used in the + # cell at (row, col) + def GetAttrByRow(self, row, col, attr): + ##self.log.write('GetAttrByRow: (%d, %d)' % (row, col)) + if col == 3: + attr.SetColour('blue') + attr.SetBold(True) + return True + return False + + + # This is called to assist with sorting the data in the view. The + # first two args are instances of the DataViewItem class, so we + # need to convert them to row numbers with the GetRow method. + # Then it's just a matter of fetching the right values from our + # data set and comparing them. The return value is -1, 0, or 1, + # just like Python's cmp() function. + def Compare(self, item1, item2, col, ascending): + if not ascending: # swap sort order? + item2, item1 = item1, item2 + row1 = self.GetRow(item1) + row2 = self.GetRow(item2) + if col == 0: + return cmp(int(self.data[row1][col]), int(self.data[row2][col])) + else: + return cmp(self.data[row1][col], self.data[row2][col]) + + + def DeleteRows(self, rows): + # make a copy since we'll be sorting(mutating) the list + rows = list(rows) + # use reverse order so the indexes don't change as we remove items + rows.sort(reverse=True) + + for row in rows: + # remove it from our data structure + del self.data[row] + # notify the view(s) using this model that it has been removed + self.RowDeleted(row) + + + def AddRow(self, value): + # update data structure + self.data.append(value) + # notify views + self.RowAppended() + + + +class TestPanel(wx.Panel): + def __init__(self, parent, log, model=None, data=None): + self.log = log + wx.Panel.__init__(self, parent, -1) + + # Create a dataview control + self.dvc = dv.DataViewCtrl(self, + style=wx.BORDER_THEME + | dv.DV_ROW_LINES # nice alternating bg colors + #| dv.DV_HORIZ_RULES + | dv.DV_VERT_RULES + | dv.DV_MULTIPLE + ) + + # Create an instance of our simple model... + if model is None: + self.model = TestModel(data, log) + else: + self.model = model + + # ...and associate it with the dataview control. Models can + # be shared between multiple DataViewCtrls, so this does not + # assign ownership like many things in wx do. There is some + # internal reference counting happening so you don't really + # need to hold a reference to it either, but we do for this + # example so we can fiddle with the model from the widget + # inspector or whatever. + self.dvc.AssociateModel(self.model) + + # Now we create some columns. The second parameter is the + # column number within the model that the DataViewColumn will + # fetch the data from. This means that you can have views + # using the same model that show different columns of data, or + # that they can be in a different order than in the model. + self.dvc.AppendTextColumn("Artist", 1, width=170, mode=dv.DATAVIEW_CELL_EDITABLE) + self.dvc.AppendTextColumn("Title", 2, width=260, mode=dv.DATAVIEW_CELL_EDITABLE) + self.dvc.AppendTextColumn("Genre", 3, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + + # There are Prepend methods too, and also convenience methods + # for other data types but we are only using strings in this + # example. You can also create a DataViewColumn object + # yourself and then just use AppendColumn or PrependColumn. + c0 = self.dvc.PrependTextColumn("Id", 0, width=40) + + # The DataViewColumn object is returned from the Append and + # Prepend methods, and we can modify some of it's properties + # like this. + c0.Alignment = wx.ALIGN_RIGHT + c0.Renderer.Alignment = wx.ALIGN_RIGHT + c0.MinWidth = 40 + + # Through the magic of Python we can also access the columns + # as a list via the Columns property. Here we'll mark them + # all as sortable and reorderable. + for c in self.dvc.Columns: + c.Sortable = True + c.Reorderable = True + + # Let's change our minds and not let the first col be moved. + c0.Reorderable = False + + # set the Sizer property (same as SetSizer) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.dvc, 1, wx.EXPAND) + + # Add some buttons to help out with the tests + b1 = wx.Button(self, label="New View", name="newView") + self.Bind(wx.EVT_BUTTON, self.OnNewView, b1) + b2 = wx.Button(self, label="Add Row") + self.Bind(wx.EVT_BUTTON, self.OnAddRow, b2) + b3 = wx.Button(self, label="Delete Row(s)") + self.Bind(wx.EVT_BUTTON, self.OnDeleteRows, b3) + + btnbox = wx.BoxSizer(wx.HORIZONTAL) + btnbox.Add(b1, 0, wx.LEFT|wx.RIGHT, 5) + btnbox.Add(b2, 0, wx.LEFT|wx.RIGHT, 5) + btnbox.Add(b3, 0, wx.LEFT|wx.RIGHT, 5) + self.Sizer.Add(btnbox, 0, wx.TOP|wx.BOTTOM, 5) + + # Bind some events so we can see what the DVC sends us + self.Bind(dv.EVT_DATAVIEW_ITEM_EDITING_DONE, self.OnEditingDone, self.dvc) + self.Bind(dv.EVT_DATAVIEW_ITEM_VALUE_CHANGED, self.OnValueChanged, self.dvc) + + + def OnNewView(self, evt): + f = wx.Frame(None, title="New view, shared model", size=(600,400)) + TestPanel(f, self.log, self.model) + b = f.FindWindowByName("newView") + b.Disable() + f.Show() + + + def OnDeleteRows(self, evt): + # Remove the selected row(s) from the model. The model will take care + # of notifying the view (and any other observers) that the change has + # happened. + items = self.dvc.GetSelections() + rows = [self.model.GetRow(item) for item in items] + self.model.DeleteRows(rows) + + + def OnAddRow(self, evt): + # Add some bogus data to a new row in the model's data + id = len(self.model.data) + 1 + value = [str(id), + 'new artist %d' % id, + 'new title %d' % id, + 'genre %d' % id] + self.model.AddRow(value) + + + def OnEditingDone(self, evt): + self.log.write("OnEditingDone\n") + + def OnValueChanged(self, evt): + self.log.write("OnValueChanged\n") + + +#---------------------------------------------------------------------- + +def main(): + from data import musicdata + + app = wx.App() + frm = wx.Frame(None, title="IndexListModel sample", size=(700,500)) + pnl = TestPanel(frm, sys.stdout, data=musicdata) + frm.Show() + app.MainLoop() + + +#---------------------------------------------------------------------- + +if __name__ == '__main__': + main() diff --git a/samples/dataview/data.py b/samples/dataview/data.py new file mode 100644 index 00000000..af58c235 --- /dev/null +++ b/samples/dataview/data.py @@ -0,0 +1,57 @@ + +musicdata = [ + [1, "Bad English", "The Price Of Love", "Rock"], + [2, "DNA featuring Suzanne Vega", "Tom's Diner", "Rock"], + [3, "George Michael", "Praying For Time", "Rock"], + [4, "Gloria Estefan", "Here We Are", "Rock"], + [5, "Linda Ronstadt", "Don't Know Much", "Rock"], + [6, "Michael Bolton", "How Am I Supposed To Live Without You", "Blues"], + [7, "Paul Young", "Oh Girl", "Rock"], + [8, "Paula Abdul", "Opposites Attract", "Rock"], + [9, "Richard Marx", "Should've Known Better", "Rock"], + [10, "Rod Stewart", "Forever Young", "Rock"], + [11, "Roxette", "Dangerous", "Rock"], + [12, "Sheena Easton", "The Lover In Me", "Rock"], + [13, "Sinead O'Connor", "Nothing Compares 2 U", "Rock"], + [14, "Stevie B.", "Because I Love You", "Rock"], + [15, "Taylor Dayne", "Love Will Lead You Back", "Rock"], + [16, "The Bangles", "Eternal Flame", "Rock"], + [17, "Wilson Phillips", "Release Me", "Rock"], + [18, "Billy Joel", "Blonde Over Blue", "Rock"], + [19, "Billy Joel", "Famous Last Words", "Rock"], + [20, "Janet Jackson", "State Of The World", "Rock"], + [21, "Janet Jackson", "The Knowledge", "Rock"], + [22, "Spyro Gyra", "End of Romanticism", "Jazz"], + [23, "Spyro Gyra", "Heliopolis", "Jazz"], + [24, "Spyro Gyra", "Jubilee", "Jazz"], + [25, "Spyro Gyra", "Little Linda", "Jazz"], + [26, "Spyro Gyra", "Morning Dance", "Jazz"], + [27, "Spyro Gyra", "Song for Lorraine", "Jazz"], + [28, "Yes", "Owner Of A Lonely Heart", "Rock"], + [29, "Yes", "Rhythm Of Love", "Rock"], + [30, "Billy Joel", "Lullabye (Goodnight, My Angel)", "Rock"], + [31, "Billy Joel", "The River Of Dreams", "Rock"], + [32, "Billy Joel", "Two Thousand Years", "Rock"], + [33, "Janet Jackson", "Alright", "Rock"], + [34, "Janet Jackson", "Black Cat", "Rock"], + [35, "Janet Jackson", "Come Back To Me", "Rock"], + [36, "Janet Jackson", "Escapade", "Rock"], + [37, "Janet Jackson", "Love Will Never Do (Without You)", "Rock"], + [38, "Janet Jackson", "Miss You Much", "Rock"], + [39, "Janet Jackson", "Rhythm Nation", "Rock"], + [40, "Cusco", "Dream Catcher", "New Age"], + [41, "Cusco", "Geronimos Laughter", "New Age"], + [42, "Cusco", "Ghost Dance", "New Age"], + [43, "Blue Man Group", "Drumbone", "New Age"], + [44, "Blue Man Group", "Endless Column", "New Age"], + [45, "Blue Man Group", "Klein Mandelbrot", "New Age"], + [46, "Kenny G", "Silhouette", "Jazz"], + [47, "Sade", "Smooth Operator", "Jazz"], + [48, "David Arkenstone", "Papillon (On The Wings Of The Butterfly)", "New Age"], + [49, "David Arkenstone", "Stepping Stars", "New Age"], + [50, "David Arkenstone", "Carnation Lily Lily Rose", "New Age"], + [51, "David Lanz", "Behind The Waterfall", "New Age"], + [52, "David Lanz", "Cristofori's Dream", "New Age"], + [53, "David Lanz", "Heartsounds", "New Age"], + [54, "David Lanz", "Leaves on the Seine", "New Age"], +] diff --git a/src/dataview_ex.py b/src/dataview_ex.py deleted file mode 100644 index d0b27b69..00000000 --- a/src/dataview_ex.py +++ /dev/null @@ -1,86 +0,0 @@ -import wx - -class DataViewItemObjectMapper(object): - """ - This class provides a mechanism for mapping between Python objects and the - DataViewItem objects used by the DataViewModel for tracking the items in - the view. The ID used for the item is the id() of the Python object. Use - `ObjectToItem` to create a DataViewItem using a Python object as its ID, - and use `ItemToObject` to fetch that Python object again later for a given - DataViewItem. - - By default a regular dictionary is used to implement the ID to object - mapping. Optionally a WeakValueDictionary can be useful when there will be - a high turnover of objects and mantaining an extra reference to the - objects would be unwise. If weak references are used then the objects - associated with data items must be weak-referenceable. (Things like - stock lists and dictionaries are not.) See `UseWeakRefs`. - - Each `PyDataViewModel` is derived from this class. - """ - def __init__(self): - self.mapper = dict() - self.usingWeakRefs = False - - def ObjectToItem(self, obj): - """ - Create a DataViewItem for the object, and remember the ID-->obj mapping. - """ - oid = id(obj) - self.mapper[oid] = obj - return DataViewItem(oid) - - def ItemToObject(self, item): - """ - Retrieve the object that was used to create an item. - """ - oid = int(item.GetID()) - return self.mapper[oid] - - def UseWeakRefs(self, flag): - """ - Switch to or from using a weak value dictionary for keeping the ID to - object map. - """ - if flag == self.usingWeakRefs: - return - if flag: - import weakref - newmap = weakref.WeakValueDictionary() - else: - newmap = dict() - newmap.update(self.mapper) - self.mapper = newmap - self.usingWeakRefs = flag - -class PyDataViewModel(PyDataViewModelBase, DataViewItemObjectMapper): - def __init__(self): - PyDataViewModelBase.__init__(self) - DataViewItemObjectMapper.__init__(self) - -NullDataViewItem = DataViewItem() - -EVT_DATAVIEW_SELECTION_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, 1) - -EVT_DATAVIEW_ITEM_ACTIVATED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, 1) -EVT_DATAVIEW_ITEM_COLLAPSING = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING, 1) -EVT_DATAVIEW_ITEM_COLLAPSED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED, 1) -EVT_DATAVIEW_ITEM_EXPANDING = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING, 1) -EVT_DATAVIEW_ITEM_EXPANDED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDED, 1) -EVT_DATAVIEW_ITEM_START_EDITING = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_START_EDITING, 1) -EVT_DATAVIEW_ITEM_EDITING_STARTED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_STARTED, 1) -EVT_DATAVIEW_ITEM_EDITING_DONE = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, 1) -EVT_DATAVIEW_ITEM_VALUE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED, 1) - -EVT_DATAVIEW_ITEM_CONTEXT_MENU = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, 1) - -EVT_DATAVIEW_COLUMN_HEADER_CLICK = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_CLICK, 1) -EVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICKED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, 1) -EVT_DATAVIEW_COLUMN_SORTED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_COLUMN_SORTED, 1) -EVT_DATAVIEW_COLUMN_REORDERED = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_COLUMN_REORDERED, 1) -EVT_DATAVIEW_CACHE_HINT = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_CACHE_HINT, 1) - -EVT_DATAVIEW_ITEM_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG, 1) -EVT_DATAVIEW_ITEM_DROP_POSSIBLE = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, 1) -EVT_DATAVIEW_ITEM_DROP = wx.PyEventBinder(wxEVT_COMMAND_DATAVIEW_ITEM_DROP, 1) - diff --git a/src/dataviewhelpers.sip b/src/dataviewhelpers.sip deleted file mode 100644 index 50046b0f..00000000 --- a/src/dataviewhelpers.sip +++ /dev/null @@ -1,143 +0,0 @@ -//-------------------------------------------------------------------------- -// Name: src/dataviewhelpers.sip -// Purpose: Implements a %MappedType for wxArrayString and wxArrayInt -// -// Author: Kevin Ollivier -// -// Created: 13-Sept-2011 -// Copyright: (c) 2011 by Kevin Ollivier -// Licence: wxWindows license -//-------------------------------------------------------------------------- - - -//-------------------------------------------------------------------------- - -// Create a C++ class for the Py version -%ModuleHeaderCode -#include - -class wxPyDataViewModelBase: public wxDataViewModel -{ -public: - wxPyDataViewModelBase() {} - - virtual unsigned int GetChildren (const wxDataViewItem &item, wxDataViewItemArray &children) const - { - return 0; - } - - virtual int Compare (const wxDataViewItem &item1, const wxDataViewItem &item2, unsigned int column, bool ascending) const - { - return 0; - } - - void GetValue( wxVariant &variant, - const wxDataViewItem &item, unsigned int col ) const - { - variant = GetValue(item, col); - } - - virtual wxVariant GetValue( const wxDataViewItem &item, unsigned int col ) const - { - return wxVariant(); - } - - virtual bool SetValue( const wxVariant &variant, - const wxDataViewItem &item, unsigned int col ) - { - return false; - } - - virtual bool GetAttr( const wxDataViewItem &item, unsigned int col, wxDataViewItemAttr &attr ) const - { - return false; - } - - virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const - { - return true; - } - - virtual unsigned int GetColumnCount() const - { - return 0; - } - - virtual wxString GetColumnType(unsigned int) const - { - return wxEmptyString; - } - - virtual wxDataViewItem GetParent(const wxDataViewItem&) const - { - return wxDataViewItem(); - } - - virtual bool IsContainer(const wxDataViewItem&) const - { - return false; - } -}; -%End - -class wxPyDataViewModelBase: wxDataViewModel -{ -public: - wxPyDataViewModelBase(); - - virtual unsigned int GetChildren (const wxDataViewItem &item, wxDataViewItemArray &children) const; - - virtual int Compare (const wxDataViewItem &item1, const wxDataViewItem &item2, unsigned int column, bool ascending) const; - - virtual wxVariant GetValue( const wxDataViewItem &item, unsigned int col ) const; - - void GetValue( wxVariant &variant, - const wxDataViewItem &item, unsigned int col ) const; - %MethodCode - Py_BEGIN_ALLOW_THREADS - // call the version that returns a variant so that it will call the Python callback - *variant = sipCpp->GetValue(*item, col); - Py_END_ALLOW_THREADS - %End - - virtual bool SetValue( const wxVariant &variant, - const wxDataViewItem &item, unsigned int col ); - - virtual bool GetAttr( const wxDataViewItem &item, unsigned int col, wxDataViewItemAttr &attr ) const; - - virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const; - - virtual unsigned int GetColumnCount() const; - - virtual wxString GetColumnType(unsigned int) const; - - virtual wxDataViewItem GetParent(const wxDataViewItem&) const; - - virtual bool IsContainer(const wxDataViewItem&) const; -}; - -/* -%MappedType wxPyLongPtr -{ - %ConvertToTypeCode - // Code to test a PyObject for compatibility - if (!sipIsErr) { - return PyLong_Check(sipPy) || wxPyInt_Check(sipPy); - } - if (PyLong_Check(sipPy) || wxPyInt_Check(sipPy)) - *sipCppPtrV = (wxPyLongPtr)PyLong_AsVoidPtr(sipPy); - - return sipGetState(sipTransferObj); - %End - - - %ConvertFromTypeCode - return PyLong_FromVoidPtr(sipCpp); - %End - -}; -*/ - -//-------------------------------------------------------------------------- - - diff --git a/src/dvcvariant.sip b/src/dvcvariant.sip new file mode 100644 index 00000000..a016a117 --- /dev/null +++ b/src/dvcvariant.sip @@ -0,0 +1,120 @@ +//-------------------------------------------------------------------------- +// Name: dvcvariant.sip +// Purpose: MappedType for wxDVCVariant +// +// Author: Robin Dunn +// +// Created: 9-Nov-2012 +// Copyright: (c) 2012 by Total Control Software +// Licence: wxWindows license +//-------------------------------------------------------------------------- + +%ModuleHeaderCode +// A wxDVCVariant is really the same thing as a wxVariant. We just create +// this new type so a different %MappedType can be created for it that also +// knows how to put DVC types in and out of a variant. +typedef wxVariant wxDVCVariant; + +wxVariant wxDVCVariant_in_helper(PyObject* source); +PyObject* wxDVCVariant_out_helper(const wxVariant& value); + +#include +typedef wxVector wxVariantVector; +%End + + +%ModuleCode +wxVariant wxDVCVariant_in_helper(PyObject* source) +{ + wxVariant ret; + + if (wxPyWrappedPtr_TypeCheck(source, wxT("wxDataViewIconText"))) { + wxDataViewIconText* ptr; + wxPyConvertWrappedPtr(source, (void**)&ptr, wxT("wxDataViewIconText")); + ret << *ptr; + } + else + ret = wxVariant_in_helper(source); + return ret; +} + + +PyObject* wxDVCVariant_out_helper(const wxVariant& value) +{ + PyObject* ret; + + if ( value.IsType("wxDataViewIconText") ) + { + wxDataViewIconText val; + val << value; + ret = wxPyConstructObject(new wxDataViewIconText(val), wxT("wxDataViewIconText"), 0); + } + else + ret = wxVariant_out_helper(value); + return ret; +} +%End + + + +%MappedType wxDVCVariant +{ + %ConvertToTypeCode + // Code to test a PyObject for compatibility. + if (!sipIsErr) { + // Any type should work since we'll just use the PyObject directly + // if the type is not one that is explicitly supported. + return TRUE; + } + + // Code to create a new wxVariant from the PyObject + wxVariant* value = new wxVariant(wxDVCVariant_in_helper(sipPy)); + *sipCppPtr = value; + return sipGetState(sipTransferObj); + %End + + + %ConvertFromTypeCode + // Code to convert a wxVariant to a PyObject. + if (sipCpp == NULL) { + return Py_None; + } else { + return wxDVCVariant_out_helper(*sipCpp); + } + %End +}; + + + +// Some DVC classes also make use of a wxVector type. This code +// will convert Python sequences to that type. +%MappedType wxVariantVector +{ + %ConvertToTypeCode + if (!sipIsErr) { + // We expect a sequence + return PySequence_Check(sipPy); + } + + wxVariantVector* vector = new wxVariantVector; + Py_ssize_t size = PySequence_Length(sipPy); + Py_ssize_t idx; + for (idx=0; idxpush_back( wxDVCVariant_in_helper(item) ); + Py_DECREF(item); + } + + *sipCppPtr = vector; + return sipGetState(sipTransferObj); + %End + + + %ConvertFromTypeCode + // no reverse convert needed yet... + return NULL; + %End +}; + + + diff --git a/src/variant.sip b/src/variant.sip index c5eeb581..b450f53d 100644 --- a/src/variant.sip +++ b/src/variant.sip @@ -1,11 +1,11 @@ //-------------------------------------------------------------------------- // Name: variant.sip -// Purpose: +// Purpose: MappedType for wxVariant // -// Author: Kevin Ollivier +// Author: Kevin Ollivier, Robin Dunn // // Created: 20-Sept-2011 -// Copyright: (c) 2011 by Kevin Ollivier +// Copyright: (c) 2012 by Kevin Ollivier, Robin Dunn // Licence: wxWindows license //-------------------------------------------------------------------------- diff --git a/src/wxpy_api.sip b/src/wxpy_api.sip index 3cad30f6..283586d7 100644 --- a/src/wxpy_api.sip +++ b/src/wxpy_api.sip @@ -280,12 +280,12 @@ wxVariant i_wxVariant_in_helper(PyObject* obj) if (PyBytes_Check(obj) || PyUnicode_Check(obj)) value = Py2wxString(obj); + else if (PyBool_Check(obj)) + value = (obj == Py_True); else if (wxPyInt_Check(obj)) value = (long)wxPyInt_AS_LONG(obj); else if (PyLong_Check(obj)) value = (long)PyLong_AsLong(obj); - else if (PyBool_Check(obj)) - value = (obj == Py_True); else if (PyFloat_Check(obj)) value = PyFloat_AS_DOUBLE(obj); else if (wxPyWrappedPtr_TypeCheck(obj, wxT("wxDateTime"))) { @@ -317,12 +317,12 @@ PyObject* i_wxVariant_out_helper(const wxVariant& value) if (value.IsType("string")) obj = wx2PyString(value.GetString()); + else if (value.IsType("bool")) + obj = PyBool_FromLong((long)value.GetBool()); else if (value.IsType("long")) obj = PyLong_FromLong(value.GetLong()); else if (value.IsType("double")) obj = PyFloat_FromDouble(value.GetDouble()); - else if (value.IsType("bool")) - obj = PyBool_FromLong((long)value.GetBool()); else if ( value.IsType("datetime") ) { wxDateTime val = value.GetDateTime(); @@ -350,7 +350,8 @@ PyObject* i_wxVariant_out_helper(const wxVariant& value) wxString msg = "Unexpected type (\"" + value.GetType() + "\") in wxVariant."; wxPyErr_SetString(PyExc_TypeError, msg.mb_str()); obj = NULL; - } + } + return obj; } diff --git a/unittests/test_dataview.py b/unittests/test_dataview.py new file mode 100644 index 00000000..3056271e --- /dev/null +++ b/unittests/test_dataview.py @@ -0,0 +1,456 @@ +import imp_unittest, unittest +import wtc +import wx +import wx.dataview as dv +import os + +pngFile = os.path.join(os.path.dirname(__file__), 'smile.png') + +#--------------------------------------------------------------------------- + + +class dataview_Tests(wtc.WidgetTestCase): + + def test_dataviewItem1(self): + dvi = dv.DataViewItem() + self.assertFalse(dvi) + + def test_dataviewItem2(self): + dvi = dv.DataViewItem(12345) + self.assertTrue(dvi) + + def test_dataviewItem3(self): + dvi1 = dv.DataViewItem(111) + dvi2 = dv.DataViewItem(222) + self.assertTrue(dvi1 != dvi2) + self.assertFalse(dvi1 == dvi2) + + def test_dataviewItem4(self): + dvi1 = dv.DataViewItem(111) + dvi2 = dv.DataViewItem(111) + self.assertTrue(dvi1 == dvi2) + self.assertFalse(dvi1 != dvi2) + + def test_dataviewItem5(self): + self.assertFalse(dv.NullDataViewItem) + + + #------------------------------------------------------- + def test_dataviewItemAttr1(self): + a = dv.DataViewItemAttr() + self.assertTrue(a.IsDefault()) + self.assertFalse(a.HasColour()) + self.assertFalse(a.HasFont()) + self.assertFalse(a.HasBackgroundColour()) + + + def test_dataviewItemAttr2(self): + a = dv.DataViewItemAttr() + a.Colour = wx.BLACK + a.BackgroundColour = wx.WHITE + a.Bold = True + a.Italic = True + self.assertFalse(a.IsDefault()) + self.assertTrue(a.HasColour()) + self.assertTrue(a.HasBackgroundColour()) + self.assertTrue(a.GetBold()) + self.assertTrue(a.GetItalic()) + + + #------------------------------------------------------- + def test_dataviewIconText1(self): + dit = dv.DataViewIconText() + icon = wx.Icon(pngFile) + dit.SetIcon(icon) + dit.SetText('Smile!') + + + def test_dataviewIconText2(self): + icon = wx.Icon(pngFile) + dit = dv.DataViewIconText('Smile!', icon) + dit.Icon + dit.Text + + + #------------------------------------------------------- + def test_dataviewModelNotifier1(self): + with self.assertRaises(TypeError): + n = dv.DataViewModelNotifier() + + + def test_dataviewModelNotifier2(self): + class MyNotifier(dv.DataViewModelNotifier): + def Cleared(self): return True + + def ItemAdded(self, parent, item): return True + def ItemChanged(self, item): return True + def ItemDeleted(self, parent, item): return True + def ItemsAdded(self, parent, items): return True + def ItemsChanged(self, items): return True + def ItemsDeleted(self, parent, items): return True + + def Resort(self): pass + def ValueChanged(self, item, col): return True + + n = MyNotifier() + + + #------------------------------------------------------- + def test_dataviewRenderer01(self): + with self.assertRaises(TypeError): + r = dv.DataViewRenderer() + + + def test_dataviewRenderer02(self): + # This one can't be subclassed (that's what dv.DataViewCustomRenderer + # is for) so make sure it raises an exception too. + with self.assertRaises(TypeError): + class MyRenderer(dv.DataViewRenderer): + def GetValue(self): return "value" + def SetValue(self, value): return True + + r = MyRenderer() + + + def test_dataviewRenderer03(self): + r = dv.DataViewTextRenderer() + + def test_dataviewRenderer04(self): + r = dv.DataViewIconTextRenderer() + + def test_dataviewRenderer05(self): + r = dv.DataViewProgressRenderer() + + def test_dataviewRenderer06(self): + r = dv.DataViewSpinRenderer(0, 100) + + def test_dataviewRenderer07(self): + r = dv.DataViewToggleRenderer() + + def test_dataviewRenderer08(self): + r = dv.DataViewDateRenderer() + + def test_dataviewRenderer09(self): + r = dv.DataViewBitmapRenderer() + + + def test_dataviewRenderer10(self): + with self.assertRaises(TypeError): + r = dv.DataViewCustomRenderer() + + def test_dataviewRenderer11(self): + class MyCustomRenderer(dv.DataViewCustomRenderer): + def GetValue(self): return "value" + def SetValue(self, value): return True + def GetSize(self): return wx.Size(100, 25) + def Render(self, cell, dc, state): return True + + r = MyCustomRenderer() + + + #------------------------------------------------------- + def test_dataviewColumn(self): + r = dv.DataViewIconTextRenderer() + # create + c = dv.DataViewColumn('title', r, 0) + # test that properties exist + c.Title + c.Bitmap + c.Width + c.MinWidth + c.Alignment + c.Flags + c.SortOrder + + self.myYield() + + #------------------------------------------------------- + def test_dataviewModel1(self): + with self.assertRaises(TypeError): + m = dv.DataViewModel() + + + def test_dataviewModel2(self): + class MyModel(dv.DataViewModel): + def GetChildren(self, item, children): return 0 + def GetColumnCount(self): return 0 + def GetColumnType(self, col): return 'string' + def GetParent(self, item): return dv.NullDataViewItem + def GetValue(self, item, col): return 'value' + def IsContainer(self, item) : return False + def SetValue(self, value, item, col): return True + + m = MyModel() + + #------------------------------------------------------- + def test_dataviewIndexListModel1(self): + with self.assertRaises(TypeError): + m = dv.DataViewIndexListModel() + + def test_dataviewIndexListModel2(self): + class MyModel(dv.DataViewIndexListModel): + def GetCount(self): return 0 + def GetRow(self, item): return 0 + def GetValueByRow(self, row, col): return 'value' + def SetValueByRow(self, value, row, col): return True + + m = MyModel() + + + def test_dataviewVirtualListModel1(self): + with self.assertRaises(TypeError): + m = dv.DataViewVirtualListModel() + + def test_dataviewVirtualModel2(self): + class MyModel(dv.DataViewVirtualListModel): + def GetCount(self): return 0 + def GetRow(self, item): return 0 + def GetValueByRow(self, row, col): return 'value' + def SetValueByRow(self, value, row, col): return True + + m = MyModel() + + + #------------------------------------------------------- + def test_dataviewCtrl1(self): + + class MyModel(dv.DataViewIndexListModel): + def GetCount(self): + return 50 + + def GetColumnCount(self): + return 10 + + def GetValueByRow(self, row, col): + return 'value(%d, %d)' (row, col) + + def SetValueByRow(self, value, row, col): + return True + + def GetColumnType(self, col): + return 'string' + + + + dvc = dv.DataViewCtrl(self.frame, style=dv.DV_ROW_LINES|dv.DV_VERT_RULES|dv.DV_MULTIPLE) + model = MyModel() + count1 = model.GetRefCount() + dvc.AssociateModel(model) + count2 = model.GetRefCount() + + # The reference count should still be 1 because the model was + # DecRef'ed when it's ownership transfered to C++ in the + # AssociateModel call + self.assertEqual(count2, 1) + self.assertTrue(count2 == count1) + + # Now try associating it with another view and check counts again + dvc2 = dv.DataViewCtrl(self.frame, style=dv.DV_ROW_LINES|dv.DV_VERT_RULES|dv.DV_MULTIPLE) + dvc2.AssociateModel(model) + self.assertEqual(model.GetRefCount(), 2) + + # Destroying the 2nd view should drop the refcount again + dvc2.Destroy() + self.assertEqual(model.GetRefCount(), 1) + + # And since ownership has been transferred, deleting this reference + # to the model should not cause any problems. + del model + + dvc.AppendTextColumn("one", 1, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + dvc.AppendTextColumn("two", 2, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + dvc.AppendTextColumn("three", 3, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + dvc.AppendTextColumn("four", 4, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + dvc.AppendTextColumn("five", 5, width=80, mode=dv.DATAVIEW_CELL_EDITABLE) + + self.frame.SendSizeEvent() + dvc.Refresh() + self.myYield() + + + + #------------------------------------------------------- + def test_dataviewListCtrl1(self): + dlc = dv.DataViewListCtrl() + dlc.Create(self.frame) + self.doListCtrlTest(dlc) + + + def test_dataviewListCtrl2(self): + dlc = dv.DataViewListCtrl(self.frame) + self.doListCtrlTest(dlc) + + def doListCtrlTest(self, dlc): + assert isinstance(dlc, dv.DataViewListCtrl) + for label in "one two three four".split(): + dlc.AppendTextColumn(label) + col = dv.DataViewColumn('five', dv.DataViewBitmapRenderer(), 4) + dlc.AppendColumn(col) + + bmp = wx.Bitmap(pngFile) + for n in range(50): + rowdata = ['%s-%02d' % (s, n) for s in "one two three four".split()] + rowdata.append(bmp) + dlc.AppendItem(rowdata) + + self.frame.SendSizeEvent() + dlc.Refresh() + self.myYield() + + + #------------------------------------------------------- + # DataViewTreeCtrl + + + def test_dataviewTreeCtrl1(self): + dtc = dv.DataViewTreeCtrl() + dtc.Create(self.frame) + self.doTreeCtrlTest(dtc) + + + def test_dataviewTreeCtrl2(self): + dtc = dv.DataViewTreeCtrl(self.frame) + self.doTreeCtrlTest(dtc) + + + def doTreeCtrlTest(self, dvtc): + isz = (16,16) + il = wx.ImageList(*isz) + fldridx = il.Add(wx.ArtProvider.GetIcon(wx.ART_FOLDER, wx.ART_OTHER, isz)) + fldropenidx = il.Add(wx.ArtProvider.GetIcon(wx.ART_FOLDER_OPEN, wx.ART_OTHER, isz)) + fileidx = il.Add(wx.ArtProvider.GetIcon(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz)) + dvtc.SetImageList(il) + + self.root = dvtc.AppendContainer(dv.NullDataViewItem, + "The Root Item", + fldridx, fldropenidx) + for x in range(15): + child = dvtc.AppendContainer(self.root, "Item %d" % x, + fldridx, fldropenidx) + for y in range(5): + last = dvtc.AppendContainer( + child, "item %d-%s" % (x, chr(ord("a")+y)), + fldridx, fldropenidx) + for z in range(5): + item = dvtc.AppendItem( + last, "item %d-%s-%d" % (x, chr(ord("a")+y), z), + fileidx) + dvtc.ExpandAncestors(item) + + self.frame.SendSizeEvent() + dvtc.Refresh() + self.myYield() + + + #------------------------------------------------------- + def test_dataviewConst(self): + dv.DVC_DEFAULT_RENDERER_SIZE + dv.DVC_DEFAULT_WIDTH + dv.DVC_TOGGLE_DEFAULT_WIDTH + dv.DVC_DEFAULT_MINWIDTH + dv.DVR_DEFAULT_ALIGNMENT + + dv.DATAVIEW_CELL_INERT + dv.DATAVIEW_CELL_ACTIVATABLE + dv.DATAVIEW_CELL_EDITABLE + dv.DATAVIEW_CELL_SELECTED + dv.DATAVIEW_CELL_PRELIT + dv.DATAVIEW_CELL_INSENSITIVE + dv.DATAVIEW_CELL_FOCUSED + + dv.DATAVIEW_COL_RESIZABLE + dv.DATAVIEW_COL_SORTABLE + dv.DATAVIEW_COL_REORDERABLE + dv.DATAVIEW_COL_HIDDEN + + dv.DV_SINGLE + dv.DV_MULTIPLE + dv.DV_NO_HEADER + dv.DV_HORIZ_RULES + dv.DV_VERT_RULES + dv.DV_ROW_LINES + dv.DV_VARIABLE_LINE_HEIGHT + + + def test_dataviewEvt1(self): + evt = dv.DataViewEvent() + + evt.GetItem + evt.SetItem + evt.GetColumn + evt.SetColumn + evt.GetModel + evt.SetModel + evt.GetValue + evt.SetValue + evt.IsEditCancelled + evt.SetEditCanceled + evt.SetDataViewColumn + evt.GetDataViewColumn + evt.GetPosition + evt.SetPosition + evt.GetCacheFrom + evt.GetCacheTo + evt.SetCache + evt.SetDataObject + evt.GetDataObject + evt.SetDataFormat + evt.GetDataFormat + evt.SetDataSize + evt.GetDataSize + evt.SetDataBuffer + evt.GetDataBuffer + evt.SetDragFlags + evt.GetDragFlags + evt.SetDropEffect + evt.GetDropEffect + + + def test_dataviewEvt2(self): + dv.wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_START_EDITING; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_STARTED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU; + dv.wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_CLICK; + dv.wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK; + dv.wxEVT_COMMAND_DATAVIEW_COLUMN_SORTED; + dv.wxEVT_COMMAND_DATAVIEW_COLUMN_REORDERED; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE; + dv.wxEVT_COMMAND_DATAVIEW_ITEM_DROP; + dv.wxEVT_COMMAND_DATAVIEW_CACHE_HINT; + + dv.EVT_DATAVIEW_SELECTION_CHANGED + dv.EVT_DATAVIEW_ITEM_ACTIVATED + dv.EVT_DATAVIEW_ITEM_COLLAPSED + dv.EVT_DATAVIEW_ITEM_EXPANDED + dv.EVT_DATAVIEW_ITEM_COLLAPSING + dv.EVT_DATAVIEW_ITEM_EXPANDING + dv.EVT_DATAVIEW_ITEM_START_EDITING + dv.EVT_DATAVIEW_ITEM_EDITING_STARTED + dv.EVT_DATAVIEW_ITEM_EDITING_DONE + dv.EVT_DATAVIEW_ITEM_VALUE_CHANGED + dv.EVT_DATAVIEW_ITEM_CONTEXT_MENU + dv.EVT_DATAVIEW_COLUMN_HEADER_CLICK + dv.EVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK + dv.EVT_DATAVIEW_COLUMN_SORTED + dv.EVT_DATAVIEW_COLUMN_REORDERED + dv.EVT_DATAVIEW_ITEM_BEGIN_DRAG + dv.EVT_DATAVIEW_ITEM_DROP_POSSIBLE + dv.EVT_DATAVIEW_ITEM_DROP + dv.EVT_DATAVIEW_CACHE_HINT + + + + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/test_pi_import.py b/unittests/test_pi_import.py index 1d406838..7912d8dd 100644 --- a/unittests/test_pi_import.py +++ b/unittests/test_pi_import.py @@ -43,6 +43,9 @@ class PIImportTest(unittest.TestCase): def test_html_pi(self): self.runPI('html.pi') + def test_dataview_pi(self): + self.runPI('dataview.pi') + #--------------------------------------------------------------------------- diff --git a/unittests/test_variant.py b/unittests/test_variant.py new file mode 100644 index 00000000..d2dd81b0 --- /dev/null +++ b/unittests/test_variant.py @@ -0,0 +1,32 @@ +import imp_unittest, unittest +import wtc +import wx + +#--------------------------------------------------------------------------- + +class variant_Tests(wtc.WidgetTestCase): + + @unittest.skipIf(not hasattr(wx, 'testVariantTypemap'), '') + def test_variant1(self): + n = wx.testVariantTypemap(123) + self.assertTrue(isinstance(n, (int, long))) + self.assertEqual(n, 123) + + + @unittest.skipIf(not hasattr(wx, 'testVariantTypemap'), '') + def test_variant2(self): + s = wx.testVariantTypemap("Hello") + self.assertEqual(s, "Hello") + + + @unittest.skipIf(not hasattr(wx, 'testVariantTypemap'), '') + def test_variant3(self): + d1 = dict(a=1, b=2, c=3) + d2 = wx.testVariantTypemap(d1) + self.assertEqual(d1, d2) + + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main()