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