1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
|
"""
Parsers used to load an app and to generate the code from a XML file.
See wcodegen.taghandler for custom tag handler base classes.
@copyright: 2002-2007 Alberto Griggio
@copyright: 2016 Carsten Grohmann
@copyright: 2016-2021 Dietmar Schwertberger
@license: MIT (see LICENSE.txt) - THIS PROGRAM COMES WITH NO WARRANTY
"""
import logging
from xml.sax import SAXException, make_parser
from xml.sax.handler import ContentHandler
import time
import common, config
class XmlParsingError(SAXException):
"Custom exception to report problems during parsing"
locator = None
def __init__(self, msg):
if self.locator:
msg = _('%s (line: %s, column: %s)') % ( msg, self.locator.getLineNumber(), self.locator.getColumnNumber() )
SAXException.__init__(self, msg)
class XmlParser(ContentHandler):
"'abstract' base class of the parsers used to load an app and to generate the code"
def __init__(self):
self._objects = Stack() # Stack of 'alive' objects
self._curr_prop = None # Name of the current property
self._curr_prop_val = [] # Value of the current property; strings, to be joined
self._appl_started = False
self.top = self._objects.top
self.parser = make_parser()
self.parser.setContentHandler(self)
self.locator = None # Document locator
self.index = None # only used with ClipboardXmlWidgetBuilder
def parse(self, source):
## Permanent workaround for Python bug "Sax parser crashes if given
## unicode file name" (http://bugs.python.org/issue11159).
## This bug causes a UnicodeEncodeError if the SAX XML parser wants to store an unicode filename internally.
## That's not a general file handling issue because the parameter source is an open file already.
self.parser.parse(source)
def parse_string(self, source):
if isinstance(source, list):
for line in source:
self.parser.feed(line)
else:
self.parser.feed(source)
self.parser.close()
def setDocumentLocator(self, locator):
self.locator = locator
XmlParsingError.locator = locator
def startElement(self, name, attrs):
raise NotImplementedError
def endElement(self, name, attrs):
raise NotImplementedError
def characters(self, data):
raise NotImplementedError
def _process_app_attrs(self, attrs):
"Process attributes of the application tag; Check only existence of attributes not the logical correctness"
res = {}
res['encoding'] = self._get_encoding(attrs)
for_version = attrs.get('for_version', '%s.%s' % config.for_version_min)
for_version_tuple = tuple([int(t) for t in for_version.split('.')[:2]])
if for_version_tuple < (2, 8):
logging.warning( _('The loaded wxGlade designs are created for wxWidgets "%s", '
'but this version is not supported anymore.'),
for_version )
logging.warning( _('The designs will be loaded and converted to wxWidgets "%s" partially. '
'Please check the designs carefully.'),
'%s.%s' % config.for_version_min )
for_version = '%s.%s' % config.for_version_min
res['for_version'] = for_version
try:
is_template = int(attrs['is_template'])
except (KeyError, ValueError):
is_template = False
res['is_template'] = is_template
res['class'] = attrs.get('class')
try:
indent_amount = int(attrs['indent_amount'])
except (KeyError, ValueError):
indent_amount = config.default_indent_amount
res['indent_amount'] = indent_amount
res['indent_symbol'] = attrs.get('indent_symbol', {" ":"space","\t":":tab"}[config.default_indent_symbol])
if 'language' in attrs:
res['language'] = attrs['language']
elif hasattr(self, 'code_writer'):
res['language'] = attrs.get('language', self.code_writer.language)
else:
res['language'] = config.default_language
res['name'] = attrs.get('name')
try:
multiple_files = bool(int(attrs['option']))
except (KeyError, ValueError):
multiple_files = config.default_multiple_files
res['option'] = multiple_files
try:
overwrite = int(attrs['overwrite'])
except (KeyError, ValueError):
overwrite = config.default_overwrite
res['overwrite'] = bool(overwrite)
try:
mark_blocks = int(attrs['mark_blocks'])
except (KeyError, ValueError):
mark_blocks = True # config.mark_blocks
if not overwrite and not mark_blocks:
mark_blocks = True
res['mark_blocks'] = bool(mark_blocks)
res['path'] = np.TextProperty._unescape( attrs.get('path') or "" )
res['header_extension'] = attrs.get('header_extension', config.default_header_extension)
res['source_extension'] = attrs.get('source_extension', config.default_source_extension)
res['top_window'] = attrs.get('top_window')
try:
use_gettext = int(attrs["use_gettext"])
except (KeyError, ValueError):
use_gettext = config.default_use_gettext
res['use_gettext'] = bool(use_gettext)
if attrs.get('use_new_namespace') == u'0' and attrs.get('language') == 'python':
logging.warning( _('The loaded wxGlade designs are created to use the old Python import style '
'("from wxPython.wx import *)". The old import style is not supported anymore.') )
logging.warning( _('The designs will be loaded and the import style will be converted to new style imports '
'("import wx"). Please check your design carefully.') )
# no update necessary - the attribute will not be used anymore
return res
def _get_encoding(self, attrs):
"Return the document encoding; attrs: Object attributes"
encoding = attrs.get('encoding', config.default_encoding)
if encoding:
try:
'a'.encode(encoding)
except LookupError:
logging.warning( _('Unknown encoding "%s", fallback to default encoding "%s"'),
encoding, config.default_encoding)
encoding = config.default_encoding
return encoding
class XmlWidgetBuilder(XmlParser):
"Parser used to build the tree of widgets from a given XML file"
def __init__(self, filename=None, input_file_version=None):
self.filename = filename
self.input_file_version = input_file_version
XmlParser.__init__(self)
def check_input_file_version(self, version):
# return True if file version is older
if not self.input_file_version: return True
return self.input_file_version[:len(version)] < version
def startElement(self, name, attrs):
if name == 'application':
# get properties of the app
self._appl_started = True
attrs = self._process_app_attrs(attrs)
app = common.root
p = app.properties
p["encoding"].set( attrs['encoding'] )
p["output_path"].set( attrs['path'] or "" )
p["class"].set( attrs['class'] or "MyApp", activate=bool(attrs.get("class")) )
p["name"].set( attrs['name'] or "app", activate=bool(attrs.get("name")) )
p["multiple_files"].set( attrs['option'] )
p["language"].set( attrs['language'] )
p["top_window"].set( attrs['top_window'] or "" )
p["use_gettext"].set( attrs['use_gettext'] )
p["is_template"].set( attrs['is_template'] )
p["overwrite"].set( attrs['overwrite'] )
p["mark_blocks"].set( attrs['mark_blocks'] )
p["indent_mode"].set( attrs['indent_symbol'] )
p["indent_amount"].set( attrs['indent_amount'] )
p["for_version"].set( attrs['for_version'] )
modified = ["encoding", "output_path", "class", "name", "multiple_files", "language", "top_window",
"use_gettext", "is_template", "overwrite", "mark_blocks",
"indent_mode", "indent_amount", "for_version"]
source_extension = attrs['source_extension']
if source_extension and source_extension[0] == '.':
p["source_extension"].set( source_extension[1:] )
modified.append( "source_extension" )
header_extension = attrs['header_extension']
if header_extension and header_extension[0] == '.':
p["header_extension"].set( header_extension[1:] )
modified.append( "header_extension" )
app.properties_changed(modified)
self._delayed_app_properties = {"for_version":attrs['for_version']}
if attrs['top_window']:
self._delayed_app_properties['top_window'] = attrs['top_window']
return
if not self._appl_started:
raise XmlParsingError(_("the root of the tree must be <application>"))
if name == 'object':
top = self.top()
if top: top.notify_owner()
# create the object and push it on the appropriate stacks
XmlWidgetObject(attrs, self)
else:
# handling of the various properties
try:
# look for a custom handler to push on the stack
obj = self.top()
handler = obj.obj.get_property_handler(name)
if handler:
obj.prop_handlers.push(handler)
# get the top custom handler and use it if there's one
handler = obj.prop_handlers.top()
if handler:
handler.start_elem(name, attrs)
except AttributeError:
pass
self._curr_prop = name
self._curr_prop_val = [] # this could be non-empty here, but that should only be spaces and newlines
def endElement(self, name):
if name == 'application':
self._appl_started = False
app = common.root
for key, value in self._delayed_app_properties.items():
app.properties[key].set( value )
app.properties_changed( sorted(self._delayed_app_properties.keys()) )
return
if name == 'object':
# remove last object from Stack
obj = self._objects.pop()
obj.notify_owner()
if obj.IS_SIZERITEM or obj.IS_SLOT:
return
if obj.sizeritem and obj.obj.parent.IS_SIZER:
# XXX just check whether obj.obj has these properties
obj.obj.copy_properties( obj.sizeritem, ("option","flag","border","span") )
obj.obj.properties["flag"]._check_value()
obj.obj.on_load()
else:
# end of a property or error
prop = self._curr_prop
data = "".join(self._curr_prop_val)
self._curr_prop = None
self._curr_prop_val = []
if prop in ("menubar", "toolbar", "statusbar"):
# case 0: ignore properties: menubar, toolbar, statusbar
return
if data:
# case 1: set _curr_prop value
try:
handler = self.top().prop_handlers.top()
if not handler or handler.char_data(data):
# if char_data returned False, we don't have to call add_property
self.top().add_property(prop, data)
except AttributeError:
pass
# case 2: call custom end_elem handler
try:
# if there is a custom handler installed for this property, call its end_elem function:
# if this returns True, remove the handler from Stack
obj = self.top()
handler = obj.prop_handlers.top()
if handler.end_elem(name):
obj.prop_handlers.pop()
obj._properties_added.append(name)
except AttributeError:
pass
def characters(self, data):
if not data: return
if self._curr_prop is None:
if data.isspace(): return
raise XmlParsingError(_("Character data can be present only inside properties"))
self._curr_prop_val.append(data)
class ProgressXmlWidgetBuilder(XmlWidgetBuilder):
"Adds support for a progress dialog to the widget builder parser"
def __init__(self, filename, input_file_version, input_file):
self.input_file = input_file
if self.input_file:
self.size = len(self.input_file.readlines())
self.input_file.seek(0)
import wx
self.progress = wx.ProgressDialog( _("Loading..."), _("Please wait while loading the app"), 20, common.main )
self.step = 4
self.i = 1
self._last_progress_update = time.time()
else:
self.size = 0
self.progress = None
XmlWidgetBuilder.__init__(self, filename, input_file_version)
def endElement(self, name):
if self.progress:
if name == 'application':
self.progress.Destroy()
self.progress = None
else:
if self.locator:
where = self.locator.getLineNumber()
value = int(round(where * 20.0 / self.size))
else:
# we don't have any information, so we update the progress bar "randomly"
value = (self.step * self.i) % 20
self.i += 1
if time.time()-self._last_progress_update > 0.25:
self.progress.Update(value)
self._last_progress_update = time.time()
XmlWidgetBuilder.endElement(self, name)
def parse(self, *args):
try:
XmlWidgetBuilder.parse(self, *args)
finally:
if self.progress:
self.progress.Destroy()
self.progress = None
def parse_string(self, *args):
try:
XmlWidgetBuilder.parse_string(self, *args)
finally:
if self.progress:
self.progress.Destroy()
self.progress = None
class _own_dict(dict):
pass # just be able to add attributes
class ClipboardXmlWidgetBuilder(XmlWidgetBuilder):
"""Parser used to cut&paste widgets.
The differences with XmlWidgetBuilder are:
- No <application> tag in the piece of xml to parse
- Fake parent, sizer and sizeritem objects to push on the three stacks:
they keep info about the destination of the hierarchy of widgets (i.e. the target of the 'paste' command)
- The first widget built must be hidden and shown again at the end of the operation"""
def __init__(self, parent, index, proportion, span, flag, border):
XmlWidgetBuilder.__init__(self)
self._renamed = {}
self._object_counter = 0
self.parent = parent
self.index = index
if not parent:
# e.g. a frame is pasted: update with the top level names
self.have_names = set(child.name for child in common.root.children)
else:
self.have_names = set(parent.toplevel_parent.names)
class XmlClipboardObject(object):
def __init__(self, **kwds):
self.IS_SIZER = self.IS_WINDOW = False
self.__dict__.update(kwds)
def notify_owner(self):
pass
# fake parent window object
fake_parent = XmlClipboardObject(obj=parent, parent=parent)
if parent and parent.IS_SIZER:
fake_parent.IS_SIZER = True
else:
fake_parent.IS_WINDOW = True
self._objects.push(fake_parent)
# fake sizer object
if parent and parent.CHILDREN!=1:
sizer = parent
fake_sizer = XmlClipboardObject(obj=sizer, parent=parent)
fake_sizer.IS_SIZER = True
sizeritem = Sizeritem()
sizeritem.properties["proportion"].set(proportion)
sizeritem.properties["span"].set(span)
sizeritem.properties["flag"].set(flag)
sizeritem.properties["border"].set(border)
sizeritem.index = index
# fake sizer item
fake_sizeritem = XmlClipboardObject(obj=sizeritem, parent=parent)
self._objects.push(fake_sizer)
self._objects.push(fake_sizeritem)
self.depth_level = 0
self._appl_started = True # no application tag when parsing from the clipboard
def _get_new_name(self, oldname):
# when pasting, check whether the name is free and if not get a new unique one
if not oldname in self.have_names:
self.have_names.add(oldname)
return oldname
if self._renamed:
# e.g. if notebook_1 was renamed to notebook_2, try notebook_1_panel_1 -> notebook_2_panel_1 first
for old,new in self._renamed.items():
if oldname.startswith(old):
newname = new + oldname[len(old):]
if not newname in self.have_names:
return newname
newname = oldname
if "_" in oldname:
# if the old name ends with an underscore and a number, just increase the number
try:
template, i = oldname.rsplit("_", 1)
i = int(i)
template = template + '_%s'
if self.parent is not None:
while newname in self.have_names:
newname = template%i
i += 1
self.have_names.add(newname)
return newname
# top level, e.g. a new frame is pasted
while newname in self.have_names:
newname = template%i
i += 1
self.have_names.add(newname)
return newname
except:
pass
# add _copy or _copy_N to the old name
if oldname.endswith('_copy'): oldname = oldname[:-5]
i = 0
if self.parent is not None:
while newname in self.have_names:
if not i:
newname = '%s_copy' % oldname
else:
newname = '%s_copy_%s' % (oldname, i)
i += 1
self.have_names.add(newname)
return newname
# top level, e.g. a new frame is pasted
while newname in self.have_names:
if not i:
newname = '%s_copy' % oldname
else:
newname = '%s_copy_%s' % (oldname, i)
i += 1
self.have_names.add(newname)
return newname
def startElement(self, name, attrs):
renamed = None
if name == 'object' and 'name' in attrs and common.widget_classes[attrs['base']].IS_NAMED:
# generate a unique name for the copy
oldname = str(attrs['name'])
newname = self._get_new_name(oldname)
attrs = _own_dict(attrs)
attrs['name'] = newname
if newname!=oldname:
attrs['original_name'] = oldname # for finding position in a virtual sizer
self._renamed[oldname] = newname
renamed = (oldname, newname)
XmlWidgetBuilder.startElement(self, name, attrs)
if renamed: self.top()._renamed = renamed
if name == 'object':
if not self.depth_level:
common.app_tree.auto_expand = False
try:
self.top_obj = self.top().obj
except AttributeError:
logging.exception( _('Exception caused by obj: %s'), self.top_obj )
self.depth_level += 1
self._object_counter += 1
def endElement(self, name):
if name == 'label':
# if e.g. a button_1 is copied to button_2, also change the label if it was "button_1"
obj = self.top()
renamed = getattr(obj, "_renamed", None)
if renamed:
oldname, newname = renamed
if obj.obj.name==newname and self._curr_prop_val and self._curr_prop_val[0]==oldname:
self._curr_prop_val[0] = newname
XmlWidgetBuilder.endElement(self, name)
if name!="object": return
self.depth_level -= 1
if not self.depth_level:
if self.parent:
self.parent.on_load(child=self.top_obj) # e.g. a GridBagSizer needs to check overlapped slots
common.app_tree.auto_expand = True
try:
if self.parent and self.parent.widget:
self.top_obj.create()
except AttributeError:
logging.exception( _('Exception caused by obj: %s'), self.top_obj )
class XMLAttrs(dict):
# raises XmlParsingError instead of KeyError
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
if 'name' in self:
raise XmlParsingError( _("attribute %s missing from object '%s'")%(key, self['name']) )
raise XmlParsingError( _("attribute %s missing from object")%key )
class XmlWidgetObject(object):
"A class to encapsulate widget attributes read from a XML file, to store them until the widget can be created"
def __init__(self, attrs, parser):
attrs = XMLAttrs(attrs)
self.prop_handlers = Stack() # a stack of custom handler functions to set properties of this object
self._properties_added = []
base = attrs.get('base', None)
klass = attrs['class']
# find sizeritem, sizer, parent window
sizeritem = sizer = parent = None
stack = parser._objects[:]
top = stack.pop(-1) if stack else None
if top and isinstance(top.obj, Sizeritem):
sizeritem = top.obj
top = stack.pop(-1)
if top and top.IS_SIZER:
sizer = top.obj
top = stack.pop(-1)
if top and top.IS_WINDOW:
parent = top.obj
while parent is None and stack:
top = stack.pop()
if top.IS_WINDOW: parent = top.obj
if parent is None:
parent = common.root
self.IS_SIZER = self.IS_WINDOW = self.IS_SLOT = self.IS_SIZERITEM = False
if base is not None:
# if base is not None, the object is a widget (or sizer), and not a sizeritem
self.sizeritem = sizeritem # the properties will be copied later in endElement
index = getattr(sizeritem, 'index', None)
if index is None and hasattr(parent, "get_itempos"):
# splitters and notebooks don't use sizeritems around their items in XML; pos is found from the name
index = parent.get_itempos(attrs)
elif not sizeritem and not stack:
index = parser.index
# build the widget
builder = common.widgets_from_xml.get(base, None)
if builder is None: raise XmlParsingError("Widget '%s' not supported."%base)
self.obj = builder(parser, base, attrs["name"], sizer or parent, index)
self.set_class_attributes(parser, attrs) # set 'class' and 'instance_class' properties, if applicable
self.IS_SIZER = self.obj.IS_SIZER
self.IS_WINDOW = self.obj.IS_WINDOW
elif klass == 'sizeritem':
self.obj = Sizeritem()
self.parent = parent
self.IS_SIZERITEM = True
elif klass == 'sizerslot':
assert sizer is not None, _("malformed wxg file: sizer slots can only be inside sizers!")
self.obj = None
self.IS_SLOT = True
sizer._add_slot(loading=True)
elif klass == 'slot':
# for a slot in a panel
self.obj = None
self.IS_SLOT = True
assert parent.CHILDREN == -1
parent._add_slot()
# push the object on the _objects stack
parser._objects.push(self)
def set_class_attributes(self, parser, attrs):
# old files: no 'instance_class' attribute, 'class' is 'multi purpose'
CLASS = self.obj.__class__
class_p = self.obj.properties.get("class")
class_v = attrs.get("class") or None
IS_BASE = class_v==CLASS.WX_CLASS or (CLASS.WX_CLASSES and class_v in CLASS.WX_CLASSES)
instance_class = attrs.get("instance_class")
if parser.check_input_file_version( (0,9,9) ):
# handle backwards compatibility
if class_p:
if class_p.deactivated is not None: # i.e. 'class' property is not mandatory / ClassPropertyD
if IS_BASE:
class_v = None
elif class_v:
if not IS_BASE or CLASS.WX_CLASS=="CustomWidget":
instance_class = class_v
class_v = None
if attrs.get("no_custom_class", None) in ('1',1):
if class_v:
instance_class = class_v
class_v = None
else:
# current file format: 'class' is always written
# if the object does not have a 'class' property at all, the value is the instance class
# if it has a 'class' property, the 'instance_class' is written if required
if class_p:
if class_p.deactivated is not None and IS_BASE:
class_v = None
else:
if not IS_BASE and instance_class is None:
instance_class = class_v
class_v = None
# update self.obj properties
modified = []
if class_v:
class_p.set( class_v, activate=True )
modified.append("class")
if instance_class:
self.obj.properties["instance_class"].set( instance_class, activate=True )
modified.append("instance_class")
if modified:
self.obj.properties_changed(modified)
def add_property(self, name, val):
"""adds a property to this widget. This method is not called if there
was a custom handler for this property, and its char_data method returned False"""
if name=="no_custom_class": return # this was also stored as attribute
if name == 'pos': # sanity check, this shouldn't happen...
logging.debug('add_property(name=pos)')
return
try:
prop = self.obj.properties[name]
except KeyError:
# unknown property for this object; issue a warning and ignore the property
if config.debugging: raise
logging.error( _("WARNING: Property '%s' not supported by this object ('%s') "), name, self.obj )
return
prop.load(val, activate=True)
self._properties_added.append(name)
def notify_owner(self):
# notify owner about the added properties
if not self._properties_added: return
self.obj.properties_changed(self._properties_added)
del self._properties_added[:]
class Stack(list):
"Simple stack implementation"
def push(self, elem):
self.append(elem)
def top(self):
return self and self[-1] or None
def count(self):
return len(self)
import new_properties as np
class Sizeritem(np.PropertyOwner):
"temporarily represents a child of a sizer"
SIZER_PROPERTIES = ["proportion","span","border","flag"]
def __init__(self):
np.PropertyOwner.__init__(self)
self.proportion = np.LayoutProportionProperty(0)
self.span = np.LayoutSpanProperty((1,1))
self.border = np.SpinProperty(0)
self.flag = np.ManagedFlags(None, name="sizeritem_flags")
def on_load(self, child=None):
pass
|