#---------------------------------------------------------------------------
# Name:        etg/dataview.py
# Author:      Kevin Ollivier
#              Robin Dunn
#
# Created:     10-Sept-2011
# Copyright:   (c) 2011 by Kevin Ollivier
# Copyright:   (c) 2011-2018 by Total Control Software
# License:     wxWindows License
#---------------------------------------------------------------------------

import etgtools
import etgtools.tweaker_tools as tools
from etgtools import PyFunctionDef, ParamDef

PACKAGE   = "wx"
MODULE    = "_dataview"
NAME      = "dataview"   # 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  = [
            'wxDataViewItem',
            'wxDataViewItemAttr',
            'wxDataViewIconText',
            'wxDataViewModelNotifier',

            'wxDataViewModel',
            'wxDataViewListModel',
            'wxDataViewIndexListModel',
            'wxDataViewVirtualListModel',

            'wxDataViewRenderer',
            'wxDataViewCustomRenderer',
            'wxDataViewTextRenderer',
            'wxDataViewIconTextRenderer',
            'wxDataViewProgressRenderer',
            'wxDataViewSpinRenderer',
            'wxDataViewToggleRenderer',
            'wxDataViewChoiceRenderer',
            #'wxDataViewChoiceByIndexRenderer',  # only available in generic dvc
            'wxDataViewDateRenderer',
            'wxDataViewBitmapRenderer',

            'wxDataViewColumn',
            'wxDataViewCtrl',
            'wxDataViewEvent',

            'wxDataViewListCtrl',
            'wxDataViewListStore',

            'wxDataViewTreeCtrl',
            'wxDataViewTreeStore',
         ]


#---------------------------------------------------------------------------

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.


    module.addHeaderCode("#include <wx/dataview.h>")

    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('int', '__bool__', '()', "return self->IsOk();")
    c.addCppMethod('long', '__hash__', '()', "return (long)self->GetID();")

    c.addCppMethod('bool', '__eq__', '(wxDataViewItem* other)',
                   "return other ? (self->GetID() == other->GetID()) : false;")
    c.addCppMethod('bool', '__ne__', '(wxDataViewItem* other)',
                   "return other ? (self->GetID() != other->GetID()) : true;")
    c.addAutoProperties()


    module.addItem(
        tools.wxArrayWrapperTemplate('wxDataViewItemArray', 'wxDataViewItem', module))
    module.addPyCode("NullDataViewItem = DataViewItem()")


    #-----------------------------------------------------------------
    c = module.find('wxDataViewModel')
    c.addAutoProperties()
    tools.fixRefCountedClass(c)

    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


    # 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
            :class:`DataViewItem` objects used by the :class:`DataViewModel` for tracking the items in
            the view. The ID used for the item is the id() of the Python object. Use
            :meth:`ObjectToItem` to create a :class:`DataViewItem` using a Python object as its ID,
            and use :meth:`ItemToObject` to fetch that Python object again later for a given
            :class:`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 :meth:`UseWeakRefs`.

            This class is used in :class:`PyDataViewModel` as a mixin for convenience.
            """,
        items=[
            PyFunctionDef('__init__', '(self)',
                body="""\
                    self.mapper = dict()
                    self.usingWeakRefs = False
                    """),

            PyFunctionDef('ObjectToItem', '(self, obj)',
                doc="Create a :class:`DataViewItem` for the object, and remember the ID-->obj mapping.",
                body="""\
                    import sys
                    oid = id(obj)
                    while oid > sys.maxsize:
                        # risk of conflict here... May need some more thought.
                        oid -= sys.maxsize
                    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 :class:`DataViewModel` combined with an object mapper.",
        items=[
            PyFunctionDef('__init__', '(self)',
                body="""\
                    DataViewModel.__init__(self)
                    DataViewItemObjectMapper.__init__(self)
                    """)
            ])


    #-----------------------------------------------------------------
    c = module.find('wxDataViewListModel')
    c.addAutoProperties()
    tools.fixRefCountedClass(c)

    # 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.find('value').type = 'wxDVCVariant&'
        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 (wxVariant& value)'),
        ('GetValueFromEditorCtrl', 'bool (wxWindow * editor, wxVariant& value)'),
        ]:
        _fixupBoolGetters(c.find(name), sig)

    m = c.find('SetValue')
    m.find('value').type = 'const wxDVCVariant&'
    m.cppSignature = 'bool (const wxVariant& value)'

    m = c.find('CreateEditorCtrl')
    m.cppSignature = 'wxWindow* (wxWindow * parent, wxRect labelRect, const wxVariant& value)'

    c.find('GetView').ignore(False)



    c = module.find('wxDataViewCustomRenderer')
    m = c.find('GetValueFromEditorCtrl')
    _fixupBoolGetters(m, 'bool (wxWindow * editor, wxVariant& value)')

    # Change the virtual method handler code to follow the same pattern as the
    # tweaked public API, namely that the value is the return value instead of
    # an out parameter.
    m.virtualCatcherCode = """\
        PyObject *sipResObj = sipCallMethod(&sipIsErr, sipMethod, "D", editor, sipType_wxWindow, NULL);
        if (sipResObj == Py_None) {
            sipRes = false;
        } else {
            sipRes = true;
            sipParseResult(&sipIsErr, sipMethod, sipResObj, "H5", sipType_wxDVCVariant, &value);
        }
        """

    c.find('GetTextExtent').ignore(False)

    m = c.find('CreateEditorCtrl')
    m.cppSignature = 'wxWindow* (wxWindow * parent, wxRect labelRect, const wxVariant& value)'


    module.addPyCode("""\
        PyDataViewCustomRenderer = wx.deprecated(DataViewCustomRenderer,
                                                 "Use DataViewCustomRenderer instead")""")


    # 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 ) [bool (const wxVariant& value)];
            virtual void GetValue( wxDVCVariant &value /Out/ ) const [bool (wxVariant& value)];
            %Property(name=Value, get=GetValue, set=SetValue)
            """, protection='public'))


    # The SpinRenderer has a few additional 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);
        """, protection='public'))


    #-----------------------------------------------------------------
    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;

        virtual void SetResizeable(bool resizable);
        virtual void SetSortable(bool sortable);
        virtual void SetReorderable(bool reorderable);
        virtual void SetHidden(bool hidden);
        """))

    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 :class:`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

    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;
            """)

    # Pythonize HitTest
    c.find('HitTest').ignore()
    c.addCppMethod('PyObject*', 'HitTest', '(const wxPoint& point)',
        isConst=True,
        doc="""\
            HitTest(point) -> (item, col)\n
            Returns the item and column located at point, as a 2 element tuple.
            """,
        body="""\
            wxDataViewItem*   item = new wxDataViewItem();;
            wxDataViewColumn* col = NULL;

            self->HitTest(*point, *item, col);

            wxPyThreadBlocker blocker;
            PyObject* value = PyTuple_New(2);
            PyObject* item_obj =
                wxPyConstructObject((void*)item, wxT("wxDataViewItem"), 1);   // owned
            PyObject* col_obj;
            if (col) {
                col_obj = wxPyConstructObject((void*)col, wxT("wxDataViewColumn"), 0);  // not owned
            } else {
                col_obj = Py_None;
                Py_INCREF(Py_None);
            }
            PyTuple_SET_ITEM(value, 0, item_obj);
            PyTuple_SET_ITEM(value, 1, col_obj);
            // PyTuple steals a reference, so we don't need to decref the items here
            return value;
            """)


    #-----------------------------------------------------------------
    c = module.find('wxDataViewEvent')
    tools.fixEventClass(c)

    c.addProperty('EditCancelled', 'IsEditCancelled', 'SetEditCanceled')

    c.find('SetCache.from').name = 'from_'
    c.find('SetCache.to').name = 'to_'

    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);
            """)

    # TODO: SetDataBuffer

    c.find('SetDataObject.obj').transfer = True


    module.addPyCode("""\
        EVT_DATAVIEW_SELECTION_CHANGED         = wx.PyEventBinder( wxEVT_DATAVIEW_SELECTION_CHANGED, 1)
        EVT_DATAVIEW_ITEM_ACTIVATED            = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_ACTIVATED, 1)
        EVT_DATAVIEW_ITEM_COLLAPSED            = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_COLLAPSED, 1)
        EVT_DATAVIEW_ITEM_EXPANDED             = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_EXPANDED, 1)
        EVT_DATAVIEW_ITEM_COLLAPSING           = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_COLLAPSING, 1)
        EVT_DATAVIEW_ITEM_EXPANDING            = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_EXPANDING, 1)
        EVT_DATAVIEW_ITEM_START_EDITING        = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_START_EDITING, 1)
        EVT_DATAVIEW_ITEM_EDITING_STARTED      = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_EDITING_STARTED, 1)
        EVT_DATAVIEW_ITEM_EDITING_DONE         = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_EDITING_DONE, 1)
        EVT_DATAVIEW_ITEM_VALUE_CHANGED        = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, 1)
        EVT_DATAVIEW_ITEM_CONTEXT_MENU         = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, 1)
        EVT_DATAVIEW_COLUMN_HEADER_CLICK       = wx.PyEventBinder( wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, 1)
        EVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK = wx.PyEventBinder( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, 1)
        EVT_DATAVIEW_COLUMN_SORTED             = wx.PyEventBinder( wxEVT_DATAVIEW_COLUMN_SORTED, 1)
        EVT_DATAVIEW_COLUMN_REORDERED          = wx.PyEventBinder( wxEVT_DATAVIEW_COLUMN_REORDERED, 1)
        EVT_DATAVIEW_ITEM_BEGIN_DRAG           = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, 1)
        EVT_DATAVIEW_ITEM_DROP_POSSIBLE        = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, 1)
        EVT_DATAVIEW_ITEM_DROP                 = wx.PyEventBinder( wxEVT_DATAVIEW_ITEM_DROP, 1)
        EVT_DATAVIEW_CACHE_HINT                = wx.PyEventBinder( wxEVT_DATAVIEW_CACHE_HINT, 1 )

        # deprecated wxEVT aliases
        wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED          = wxEVT_DATAVIEW_SELECTION_CHANGED
        wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED             = wxEVT_DATAVIEW_ITEM_ACTIVATED
        wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED             = wxEVT_DATAVIEW_ITEM_COLLAPSED
        wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDED              = wxEVT_DATAVIEW_ITEM_EXPANDED
        wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING            = wxEVT_DATAVIEW_ITEM_COLLAPSING
        wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING             = wxEVT_DATAVIEW_ITEM_EXPANDING
        wxEVT_COMMAND_DATAVIEW_ITEM_START_EDITING         = wxEVT_DATAVIEW_ITEM_START_EDITING
        wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_STARTED       = wxEVT_DATAVIEW_ITEM_EDITING_STARTED
        wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE          = wxEVT_DATAVIEW_ITEM_EDITING_DONE
        wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED         = wxEVT_DATAVIEW_ITEM_VALUE_CHANGED
        wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU          = wxEVT_DATAVIEW_ITEM_CONTEXT_MENU
        wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_CLICK        = wxEVT_DATAVIEW_COLUMN_HEADER_CLICK
        wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK  = wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK
        wxEVT_COMMAND_DATAVIEW_COLUMN_SORTED              = wxEVT_DATAVIEW_COLUMN_SORTED
        wxEVT_COMMAND_DATAVIEW_COLUMN_REORDERED           = wxEVT_DATAVIEW_COLUMN_REORDERED
        wxEVT_COMMAND_DATAVIEW_CACHE_HINT                 = wxEVT_DATAVIEW_CACHE_HINT
        wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG            = wxEVT_DATAVIEW_ITEM_BEGIN_DRAG
        wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE         = wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE
        wxEVT_COMMAND_DATAVIEW_ITEM_DROP                  = wxEVT_DATAVIEW_ITEM_DROP
        """)

    #-----------------------------------------------------------------
    c = module.find('wxDataViewListCtrl')
    tools.fixWindowClass(c)

    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()


    #-----------------------------------------------------------------
    def _setClientDataTrasfer(klass):
        for item in klass.allItems():
            if isinstance(item, ParamDef) and item.type == 'wxClientData *':
                item.transfer = True

    c = module.find('wxDataViewTreeCtrl')
    tools.fixWindowClass(c)
    _setClientDataTrasfer(c)
    c.find('GetStore').overloads = []

    c = module.find('wxDataViewTreeStore')
    c.addAutoProperties()
    tools.fixRefCountedClass(c)
    _setClientDataTrasfer(c)


    #-----------------------------------------------------------------
    tools.doCommonTweaks(module)
    tools.runGenerators(module)


#---------------------------------------------------------------------------
if __name__ == '__main__':
    run()

