1#---------------------------------------------------------------------------
2# Name:        etg/propgridiface.py
3# Author:      Robin Dunn
4#
5# Created:     23-Feb-2015
6# Copyright:   (c) 2015-2018 by Total Control Software
7# License:     wxWindows License
8#---------------------------------------------------------------------------
9
10import etgtools
11import etgtools.tweaker_tools as tools
12
13PACKAGE   = "wx"
14MODULE    = "_propgrid"
15NAME      = "propgridiface"   # Base name of the file to generate to for this script
16DOCSTRING = ""
17
18# The classes and/or the basename of the Doxygen XML files to be processed by
19# this script.
20ITEMS  = [ 'wxPGPropArgCls',
21           'wxPropertyGridInterface',
22           ]
23
24#---------------------------------------------------------------------------
25
26def run():
27    # Parse the XML file(s) building a collection of Extractor objects
28    module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
29    etgtools.parseDoxyXML(module, ITEMS)
30
31    #-----------------------------------------------------------------
32    # Tweak the parsed meta objects in the module object as needed for
33    # customizing the generated code and docstrings.
34
35    c = module.find('wxPGPropArgCls')
36    assert isinstance(c, etgtools.ClassDef)
37    c.find('wxPGPropArgCls').findOverload('wxString &').ignore()
38    c.find('wxPGPropArgCls').findOverload('char *').ignore()
39    c.find('wxPGPropArgCls').findOverload('wchar_t *').ignore()
40    c.find('wxPGPropArgCls').findOverload('int').ignore()
41    c.find('wxPGPropArgCls').findOverload('deallocPtr').ignore()
42
43    # Make a string ctor that uses the wxPython-specific version of
44    # the C++ class' ctor
45    newCtor = c.addCppCtor('(const wxString& str)',
46        doc="Creates a PGPropArgCls from a string.",
47        body="""\
48            wxString* name = new wxString(*str);
49            return new wxPGPropArgCls(name, true);
50        """
51        )
52
53    # Make it be the first overload instead of the last
54    ctor = c.find('wxPGPropArgCls')
55    overloads = list(ctor.overloads)
56    del overloads[overloads.index(newCtor)]
57    overloads.insert(0, newCtor)
58    ctor.overloads = overloads
59
60
61    c.find('GetPtr').overloads[0].ignore()
62
63    c.convertFromPyObject = """\
64        // Code to test a PyObject for compatibility with wxPGPropArgCls
65        if (!sipIsErr) {
66            if (sipCanConvertToType(sipPy, sipType_wxPGPropArgCls, SIP_NO_CONVERTORS))
67                return TRUE;
68            if (PyBytes_Check(sipPy) || PyUnicode_Check(sipPy))
69                return TRUE;
70            if (sipPy == Py_None)
71                return TRUE;
72            if (sipCanConvertToType(sipPy, sipType_wxPGProperty, SIP_NO_CONVERTORS))
73                return TRUE;
74            return FALSE;
75        }
76
77        // Code to convert a compatible PyObject to a wxPGPropArgCls
78        if (PyBytes_Check(sipPy) || PyUnicode_Check(sipPy)) {
79            wxString* name = new wxString(Py2wxString(sipPy));
80            *sipCppPtr = new wxPGPropArgCls(name, true);
81            return sipGetState(sipTransferObj);
82        }
83        else if (sipCanConvertToType(sipPy, sipType_wxPGProperty, SIP_NO_CONVERTORS)) {
84            int state = 0;
85            wxPGProperty* prop = reinterpret_cast<wxPGProperty*>(
86                sipConvertToType(sipPy, sipType_wxPGProperty, sipTransferObj, SIP_NO_CONVERTORS, &state, sipIsErr));
87            *sipCppPtr = new wxPGPropArgCls(prop);
88            sipReleaseType(prop, sipType_wxPGProperty, state);
89            return sipGetState(sipTransferObj);
90        }
91        else if (sipPy == Py_None) {
92            *sipCppPtr = new wxPGPropArgCls(static_cast< wxPGProperty * >(NULL));
93            return sipGetState(sipTransferObj);
94        }
95        else {
96            // It's already a wxPGPropArgCls, just fetch the pointer and return
97            *sipCppPtr = reinterpret_cast<wxPGPropArgCls*>(sipConvertToType(
98                sipPy, sipType_wxPGPropArgCls, sipTransferObj,
99                SIP_NO_CONVERTORS, 0, sipIsErr));
100            return 0; // not a new instance
101        }
102        """
103
104
105    #----------------------------------------------------------
106
107    c = module.find('wxPropertyGridInterface')
108    c.abstract = True
109    for m in c.findAll('GetIterator'):
110        if m.type == 'wxPropertyGridConstIterator':
111            m.ignore()
112
113    spv = c.find('SetPropertyValue')
114    spv.findOverload('int value').ignore()
115    spv.findOverload('wxLongLong_t value').ignore()
116    spv.findOverload('wxULongLong_t value').ignore()
117    spv.findOverload('wxObject *value').ignore()
118
119    # Reorder SetPropertyValue overloads so the one taking a long int is not
120    # first. Mark others that could be auto-converted from int as
121    # "constrained" so they will only be used for that specific type. This
122    # should result in SetPropertyValue(id, double) only used for floats and
123    # not ints, or other things that can convert to int.
124    spv.findOverload('bool value').find('value').constrained = True
125    spv.findOverload('double value').find('value').constrained = True
126    spv_long = spv.findOverload('long value')
127    spv_long.ignore()
128    spv.reorderOverloads() # Ensures an ignored item is not first,
129    spv_long.ignore(False) # and then we can unignore it.
130
131
132    c.find('Append.property').transfer = True
133    c.find('AppendIn.newProperty').transfer = True
134    for m in c.find('Insert').all():
135        m.find('newProperty').transfer = True
136
137
138
139    # Tons of Python method implementations ported from Classic...
140
141    module.addPyCode("""\
142        _type2property = None
143        _vt2getter = None
144        """)
145
146    c.addPyMethod('MapType', '(self, class_, factory)',
147        doc="""\
148            Registers Python type/class to property mapping.
149
150            :param `factory`: Property builder function/class.
151            """,
152        body="""\
153            global _type2property
154            if _type2property is None:
155                raise AssertionError("call only after a propertygrid or "
156                                     "manager instance constructed")
157            _type2property[class_] = factory
158            """)
159
160
161    c.addPyMethod('DoDefaultTypeMappings', '(self)',
162        doc="Add built-in properties to the map.",
163        body="""\
164            import sys
165            global _type2property
166            if _type2property is not None:
167                return
168            _type2property = dict()
169
170            _type2property[str] = StringProperty
171            if sys.version_info.major < 2:
172                _type2property[unicode] = StringProperty
173            _type2property[int] = IntProperty
174            _type2property[float] = FloatProperty
175            _type2property[bool] = BoolProperty
176            _type2property[list] = ArrayStringProperty
177            _type2property[tuple] = ArrayStringProperty
178            _type2property[wx.Font] = FontProperty
179            _type2property[wx.Colour] = ColourProperty
180            #_type2property[wx.Size] = SizeProperty
181            #_type2property[wx.Point] = PointProperty
182            #_type2property[wx.FontData] = FontDataProperty
183            """)
184
185
186    # TODO: is this still needed?
187    c.addPyMethod('DoDefaultValueTypeMappings', '(self)',
188        doc="Map pg value type ids to getter methods.",
189        body="""\
190            global _vt2getter
191            if _vt2getter is not None:
192                return
193            _vt2getter = dict()
194        """)
195
196
197    c.find('GetPropertyValues').ignore()
198    c.addPyMethod('GetPropertyValues',
199        '(self, dict_=None, as_strings=False, inc_attributes=False)',
200        doc="""\
201            Returns all property values in the grid.\n
202            :param `dict_`: A to fill with the property values. If not given,
203                then a new one is created. The dict_ can be an object as well,
204                in which case it's __dict__ is used.
205            :param `as_strings`: if True, then string representations of values
206                are fetched instead of native types. Useful for config and such.
207            :param `inc_attributes`: if True, then property attributes are added
208                in the form of "@<propname>@<attr>".
209            :returns: A dictionary with values. It is always a dictionary,
210                so if dict_ was and object with __dict__ attribute, then that
211                attribute is returned.
212            """,
213        body="""\
214            if dict_ is None:
215                dict_ = {}
216            elif hasattr(dict_,'__dict__'):
217                dict_ = dict_.__dict__
218
219            getter = self.GetPropertyValue if not as_strings else self.GetPropertyValueAsString
220
221            it = self.GetVIterator(PG_ITERATE_PROPERTIES)
222            while not it.AtEnd():
223                p = it.GetProperty()
224                name = p.GetName()
225                dict_[name] = getter(p)
226
227                if inc_attributes:
228                    attrs = p.GetAttributes()
229                    if attrs and len(attrs):
230                        dict_['@%s@attr'%name] = attrs
231
232                it.Next()
233
234            return dict_
235            """)
236
237
238    for m in c.find('SetPropertyValues').all():
239        m.ignore()
240    c.addPyMethod('SetPropertyValues', '(self, dict_, autofill=False)',
241        doc="""\
242            Sets property values from a dictionary.\n
243            :param `dict_`: the source of the property values to set, which can be
244                either a dictionary or an object with a __dict__ attribute.
245            :param `autofill`: If true, keys with not relevant properties are
246                auto-created. For more info, see :method:`AutoFill`.
247
248            :note:
249              * Keys starting with underscore are ignored.
250              * Attributes can be set with entries named like "@<propname>@<attr>".
251            """,
252        body="""\
253            if dict_ is None:
254                dict_ = {}
255            elif hasattr(dict_,'__dict__'):
256                dict_ = dict_.__dict__
257            attr_dicts = []
258
259            def set_sub_obj(k0, dict_):
260                for k,v in dict_.items():
261                    if k[0] != '_':
262                        if k.endswith('@attr'):
263                            attr_dicts.append((k[1:-5],v))
264                        else:
265                            try:
266                                self.SetPropertyValue(k,v)
267                            except:
268                                try:
269                                    if autofill:
270                                        self._AutoFillOne(k0,k,v)
271                                        continue
272                                except:
273                                    if isinstance(v,dict):
274                                        set_sub_obj(k,v)
275                                    elif hasattr(v,'__dict__'):
276                                        set_sub_obj(k,v.__dict__)
277
278                for k,v in attr_dicts:
279                    p = self.GetPropertyByName(k)
280                    if not p:
281                        raise AssertionError("No such property: '%s'"%k)
282                    for an,av in v.items():
283                        p.SetAttribute(an, av)
284
285
286            cur_page = False
287            is_manager = isinstance(self, PropertyGridManager)
288
289            try:
290                set_sub_obj(self.GetGrid().GetRoot(), dict_)
291            except:
292                import traceback
293                traceback.print_exc()
294
295            self.Refresh()
296            """)
297
298    # TODO: should these be marked as deprecated?
299    module.addPyCode("""\
300        PropertyGridInterface.GetValues = PropertyGridInterface.GetPropertyValues
301        PropertyGridInterface.SetValues = PropertyGridInterface.SetPropertyValues
302        """)
303
304
305    c.addPyMethod('_AutoFillMany', '(self,cat,dict_)',
306        body="""\
307            for k,v in dict_.items():
308                self._AutoFillOne(cat,k,v)
309            """)
310
311    c.addPyMethod('_AutoFillOne', '(self,cat,k,v)',
312        body="""\
313            global _type2property
314            factory = _type2property.get(v.__class__,None)
315            if factory:
316                self.AppendIn(cat, factory(k,k,v))
317            elif hasattr(v,'__dict__'):
318                cat2 = self.AppendIn(cat, PropertyCategory(k))
319                self._AutoFillMany(cat2, v.__dict__)
320            elif isinstance(v, dict):
321                cat2 = self.AppendIn(cat, PropertyCategory(k))
322                self._AutoFillMany(cat2, v)
323            elif not k.startswith('_'):
324                raise AssertionError("member '%s' is of unregistered type/"
325                                     "class '%s'"%(k,v.__class__))
326            """)
327
328    c.addPyMethod('AutoFill', '(self, obj, parent=None)',
329        doc="""\
330            "Clears properties and re-fills to match members and values of
331            the given object or dictionary obj.
332            """,
333        body="""\
334            self.edited_objects[parent] = obj
335
336            cur_page = False
337            is_manager = isinstance(self, PropertyGridManager)
338
339            if not parent:
340                if is_manager:
341                    page = self.GetCurrentPage()
342                    page.Clear()
343                    parent = page.GetRoot()
344                else:
345                    self.Clear()
346                    parent = self.GetGrid().GetRoot()
347            else:
348                it = self.GetIterator(PG_ITERATE_PROPERTIES, parent)
349                it.Next()  # Skip the parent
350                while not it.AtEnd():
351                    p = it.GetProperty()
352                    if not p.IsSomeParent(parent):
353                        break
354
355                    self.DeleteProperty(p)
356
357                    name = p.GetName()
358                    it.Next()
359
360            if not is_manager or page == self.GetCurrentPage():
361                self.Freeze()
362                cur_page = True
363
364            try:
365                self._AutoFillMany(parent,obj.__dict__)
366            except:
367                import traceback
368                traceback.print_exc()
369
370            if cur_page:
371                self.Thaw()
372            """)
373
374
375    c.addPyMethod('RegisterEditor', '(self, editor, editorName=None)',
376        doc="Register a new editor, either an instance or a class.",
377        body="""\
378            if not isinstance(editor, PGEditor):
379                editor = editor()
380            if not editorName:
381                editorName = editor.__class__.__name__
382            try:
383                self._editor_instances.append(editor)
384            except:
385                self._editor_instances = [editor]
386            return PropertyGrid.DoRegisterEditorClass(editor, editorName)
387            """
388        )
389
390
391    c.find('GetPropertyClientData').ignore()
392    c.addPyMethod('GetPropertyClientData', '(self, p)',
393        body="""\
394            if isinstance(p, str):
395                p = self.GetPropertyByName(p)
396            return p.GetClientData()
397            """)
398
399    c.find('SetPropertyClientData').ignore()
400    c.addPyMethod('SetPropertyClientData', '(self, p, data)',
401        body="""\
402            if isinstance(p, str):
403                p = self.GetPropertyByName(p)
404            return p.SetClientData(data)
405            """)
406
407
408
409    c.addPyMethod('GetPyIterator', '(self, flags=PG_ITERATE_DEFAULT, firstProperty=None)',
410        doc="""\
411            Returns a pythonic property iterator for a single :ref:`PropertyGrid`
412            or page in :ref:`PropertyGridManager`. Arguments are same as for
413            :ref:`GetIterator`.
414
415            The following example demonstrates iterating absolutely all items in
416            a single grid::
417
418                iterator = propGrid.GetPyIterator(wx.propgrid.PG_ITERATE_ALL)
419                for prop in iterator:
420                    print(prop)
421
422            :see: `wx.propgrid.PropertyGridInterface.Properties`
423                  `wx.propgrid.PropertyGridInterface.Items`
424            """,
425        body="""\
426            it = self.GetIterator(flags, firstProperty)
427            while not it.AtEnd():
428                yield it.GetProperty()
429                it.Next()
430            """)
431
432
433    c.addPyMethod('GetPyVIterator', '(self, flags=PG_ITERATE_DEFAULT)',
434        doc="""\
435            Similar to :ref:`GetVIterator` but returns a pythonic iterator.
436            """,
437        body="""\
438            it = self.GetVIterator(flags)
439            while not it.AtEnd():
440                yield it.GetProperty()
441                it.Next()
442            """)
443
444
445    c.addPyMethod('_Properties', '(self)',
446        doc="""\
447            This attribute is a pythonic iterator over all properties in
448            this `PropertyGrid` property container. It will only skip
449            categories and private child properties. Usage is simple::
450
451                for prop in propGrid.Properties:
452                    print(prop)
453
454            :see: `wx.propgrid.PropertyGridInterface.Items`
455                  `wx.propgrid.PropertyGridInterface.GetPyIterator`
456            """,
457        body="""\
458            it = self.GetIterator(PG_ITERATE_NORMAL)
459            while not it.AtEnd():
460                yield it.GetProperty()
461                it.Next()
462            """)
463    c.addPyProperty('Properties', '_Properties')
464
465
466    c.addPyMethod('_Items', '(self)',
467        doc="""\
468            This attribute is a pythonic iterator over all items in this
469            `PropertyGrid` property container, excluding only private child
470            properties. Usage is simple::
471
472                for prop in propGrid.Items:
473                    print(prop)
474
475            :see: `wx.propgrid.PropertyGridInterface.Properties`
476                  `wx.propgrid.PropertyGridInterface.GetPyVIterator`
477            """,
478        body="""\
479            it = self.GetVIterator(PG_ITERATE_NORMAL | PG_ITERATE_CATEGORIES)
480            while not it.AtEnd():
481                yield it.GetProperty()
482                it.Next()
483            """)
484    c.addPyProperty('Items', '_Items')
485
486
487    #----------------------------------------------------------
488
489    module.addItem(
490        tools.wxArrayPtrWrapperTemplate('wxArrayPGProperty', 'wxPGProperty', module))
491
492
493
494    # wxPGPropArg is a typedef for "const wxPGPropArgCls&" so having the
495    # wrappers treat it as a normal type can be problematic. ("new cannot be
496    # applied to a reference type", etc.) Let's just ignore it and replace it
497    # everywhere for the real type.
498    module.find('wxPGPropArg').ignore()
499    for item in module.allItems():
500        if hasattr(item, 'type') and item.type == 'wxPGPropArg':
501            item.type = 'const wxPGPropArgCls &'
502
503
504    # Switch all wxVariant types to wxPGVariant, so the propgrid-specific
505    # version of the MappedType will be used for converting to/from Python
506    # objects.
507    for item in module.allItems():
508        if hasattr(item, 'type') and 'wxVariant' in item.type:
509            item.type = item.type.replace('wxVariant', 'wxPGVariant')
510
511
512    #-----------------------------------------------------------------
513    tools.doCommonTweaks(module)
514    tools.runGenerators(module)
515
516
517#---------------------------------------------------------------------------
518if __name__ == '__main__':
519    run()
520
521