"""\ XRC code generator Generates the xml code for the app in XRC format. Calls the appropriate ``writers'' of the various objects. These functions return an instance of XrcObject. To be done: write XRC directly instead of using BaseLangCodeWriter as base. @copyright: 2002-2007 Alberto Griggio @copyright: 2012-2016 Carsten Grohmann @copyright: 2019-2020 Dietmar Schwertberger @license: MIT (see LICENSE.txt) - THIS PROGRAM COMES WITH NO WARRANTY """ from xml.sax.saxutils import escape, quoteattr from codegen import BaseLangCodeWriter from collections import OrderedDict import common, wcodegen import new_properties as np import logging class XrcObject(wcodegen.XrcWidgetCodeWriter): "Class to produce the XRC code for a given widget. This is a base class which does nothing" def __init__(self, klass=None): wcodegen.XrcWidgetCodeWriter.__init__(self, klass) self.properties = {} self.children = [] # sub-objects def write_child_prologue(self, child, output, ntabs): pass def write_child_epilogue(self, child, output, ntabs): pass def write_property(self, name, val, outfile, ntabs): pass def write(self, output, ntabs, properties=None): pass def warning(self, msg): "Show a warning message" logging.warning(msg) class SizerItemXrcObject(XrcObject): "XrcObject to handle sizer items" def __init__(self, xrc_obj, obj): XrcObject.__init__(self) self.xrc_obj = xrc_obj self.obj = obj def write(self, output, ntabs, properties=None): tabs = self.tabs(ntabs) tabs1 = self.tabs(ntabs + 1) output.append(tabs + '<object class="sizeritem">\n') if self.obj.proportion: output.append(tabs1 + '<option>%s</option>\n' % self.obj.proportion) flag = self.obj.properties["flag"].get_string_value() if flag and flag!='0': output.append(tabs1 + '<flag>%s</flag>\n' % self.cn_f(flag)) if self.obj.border: output.append(tabs1 + '<border>%s</border>\n' % self.obj.border) if self.obj.parent._IS_GRIDBAG: cellpos = self.obj.parent._get_row_col(self.obj.index) output.append( tabs1 + '<cellpos>%d,%d</cellpos>\n' % cellpos ) output.append( tabs1 + '<cellspan>%d,%d</cellspan>\n' % self.obj.span ) # write the widget self.xrc_obj.write(output, ntabs + 1) output.append(tabs + '</object>\n') class SpacerXrcObject(XrcObject): "XrcObject to handle widgets" def __init__(self, obj): XrcObject.__init__(self) self.obj = obj def write(self, output, ntabs): obj = self.obj tabs = self.tabs(ntabs) tabs1 = self.tabs(ntabs + 1) if obj is None or obj.name == "spacer": output.append( tabs + '<object class="spacer">\n' ) else: output.append( tabs + '<object class="spacer" name=%s>\n'%quoteattr(obj.name) ) if obj is not None: # a real spacer output.append( tabs1 + '<size>%s, %s</size>\n' % (obj.width, obj.height) ) if obj.proportion: output.append(tabs1 + '<option>%s</option>\n' % obj.proportion) if obj.flag: flag = obj.properties["flag"].get_string_value() output.append(tabs1 + '<flag>%s</flag>\n' % self.cn_f(flag)) if obj.border: output.append(tabs1 + '<border>%s</border>\n' % obj.border) else: # just an empty sizer slot output.append( tabs1 + '<size>0, 0</size>\n' ) output.append(tabs + '</object>\n') class DefaultXrcObject(XrcObject): "Standard XrcObject for every widget, used if no specific XrcObject is available" def __init__(self, widget): classname = widget.get_prop_value("class", widget.WX_CLASS) XrcObject.__init__(self, classname) self.widget = widget self.name = widget.name self.klass = widget.WX_CLASS self.subclass = classname def write_property(self, name, val, output, ntabs): if not val: return if isinstance(val, np.BitmapProperty): # rename: no '..._bitmap' and some renames if name.endswith('_bitmap'): name = name[:-7] if name=="pressed": name = "selected" elif name=="current": name = "hover" prop = self._format_bitmap_property(name, val.get_value(), ntabs) else: prop = common.format_xml_prop(name, val, ntabs) output.append(prop) def _format_bitmap_property(self, name, val, ntabs): "Return formatted bitmap/icon XRC property (as string)." if val.startswith('art:'): content = val[4:] elements = [item.strip() for item in content.split(',')] art_id = elements[0] art_client = elements[1] if art_client != 'wxART_OTHER': return common.format_xml_prop( name, '', ntabs, stock_id=art_id, stock_client=art_client ) else: return common.format_xml_prop(name, u'', ntabs, stock_id=art_id) elif val.startswith('code:') or val.startswith('empty:') or val.startswith('var:'): logging.warn( _('XRC: Unsupported bitmap statement "%s" for %s "%s"'), val, self.klass, self.name ) return None return common.format_xml_prop(name, val, ntabs) def write(self, output, ntabs, properties=None): if properties is None: properties = {} if "name" in properties: name = properties["name"] del properties["name"] else: name = self.name if self.widget.IS_SIZER: output.append(self.tabs(ntabs) + '<object class=%s>\n' % quoteattr(self.klass)) else: if self.subclass and self.subclass != self.klass: output.append(self.tabs(ntabs) + '<object class=%s name=%s subclass=%s>\n' % ( quoteattr(self.klass), quoteattr(name), quoteattr(self.subclass)) ) else: output.append(self.tabs(ntabs) + '<object class=%s name=%s>\n' % (quoteattr(self.klass), quoteattr(name))) tab_str = self.tabs(ntabs + 1) # write the properties import edit_sizers active_properties = self.widget.get_properties(without=set(edit_sizers.SizerBase.MANAGED_PROPERTIES)) font = None for prop in active_properties: if not prop.is_active(): continue if prop.value==prop.default_value: continue name = prop.name if name in properties: continue # set already value = None if name=='foreground': value = prop.get_string_value() if not value.startswith('#'): # XRC does not support colors from system settings continue name = 'fg' elif name=='background': value = prop.get_string_value() if not value.startswith('#'): # XRC does not support colors from system settings continue name = "bg" elif name=='font': font = prop.value continue elif name=="style": if hasattr(prop, "value_set"): if prop.value_set==prop.default_value: continue value = prop.get_string_value() if value: value = self.cn_f(value) elif name=='id': continue # id has no meaning for XRC elif name=='events': for win_id, event, handler, event_type in self.get_event_handlers(self.widget): output.append(tab_str + '<handler event=%s>%s</handler>\n' % (quoteattr(event), escape(handler))) continue elif name=='disabled': # 'disabled' property is actually 'enabled' for XRC if prop.get(): properties['enabled'] = '0' continue elif name=='custom_base' in self.properties: # custom base classes are ignored for XRC... continue elif name=='extraproperties': value = prop.get() if value: properties.update(value) continue if isinstance(prop, np.BitmapProperty): value = prop else: if value is None: value = prop.get_string_value() if value is None: continue properties[name] = value for name in sorted( properties.keys() ): value = properties[name] if value is None: continue self.write_property( name, value, output, ntabs + 1) # write the font, if present if font: output.append(tab_str + '<font>\n') tab_str = self.tabs(ntabs + 2) data = sorted( zip(['size','family','style','weight','underlined','face'], font) ) for key, val in data: if isinstance(val, int): val = str(val) if val: output.append(tab_str + '<%s>%s</%s>\n' % (escape(key), escape(val), escape(key))) output.append(self.tabs(ntabs + 1) + '</font>\n') # write the children for c in self.children: self.write_child_prologue(c, output, ntabs + 1) c.write(output, ntabs + 1) self.write_child_epilogue(c, output, ntabs + 1) output.append(self.tabs(ntabs) + '</object>\n') class NotImplementedXrcObject(XrcObject): """XrcObject used when no code for the widget can be generated (for example, because XRC does not currently handle such widget)""" def __init__(self, code_obj): XrcObject.__init__(self) self.code_obj = code_obj def write(self, output, ntabs): msg = 'code generator for %s objects not available' % self.code_obj.WX_CLASS self.warning('%s' % msg) output.append( '%s%s\n' % (self.tabs(ntabs), self._format_comment(msg)) ) class XRCCodeWriter(BaseLangCodeWriter, wcodegen.XRCMixin): "Code writer class for writing XRC XML code out of the designed GUI elements" # dict of active XrcObject instances: during the code generation it stores all the non-sizer objects that have # children (i.e. frames, dialogs, panels, notebooks, etc.), while at the end of the code generation, # before finalize is called, it contains only the true toplevel objects (frames and dialogs), and is used to write # their XML code (see finalize). The other objects are deleted when add_object is called with their corresponding # code_object as argument (see add_object) xrc_objects = None property_writers = {} # dict of dicts of property handlers specific for a widget; keys: class names of the widgets obj_builders = {} # Dictionary of ``writers'' for the various objects tmpl_encoding = '<?xml version="1.0" encoding="%s"?>\n' tmpl_generated_by = '<!-- %(generated_by)s -->' use_names_for_binding_events = False # inject different XRC objects XrcObject = XrcObject SizerItemXrcObject = SizerItemXrcObject SpacerXrcObject = SpacerXrcObject DefaultXrcObject = DefaultXrcObject NotImplementedXrcObject = NotImplementedXrcObject def __init__(self): BaseLangCodeWriter.__init__(self) # Inject to all classed derived from WrcObject if not hasattr(XrcObject, 'tabs'): XrcObject.tabs = self.tabs if not hasattr(XrcObject, '_format_comment'): XrcObject._format_comment = self._format_comment def init_lang(self, app): # for now we handle only single-file code generation if self.multiple_files: return "XRC code cannot be split into multiple files" # overwrite existing sources always self._overwrite = True self.output_file_name = app.output_path self.out_file = [] self.out_file.append('\n<resource version="2.3.0.1">\n') self.curr_tab = 1 self.xrc_objects = OrderedDict() def finalize(self): # write the code for every toplevel object for obj in self.xrc_objects.values(): obj.write(self.out_file, 1) self.out_file.append('</resource>\n') # store the contents to file self.save_file( self.output_file_name, self.out_file ) self.out_file = None def generate_code(self, root, widget=None): "entry point for recursive code generation via _generate_code()" # root must be application.Application instance for now for c in root.children or []: if widget is not None and c is not widget: continue # for preview self._generate_code(None, None, None, c) def _generate_code(self, klass, parent, parent_builder, obj): # XXX old implementation from __init__.py before re-factoring 'real' code generation # recursively generate code, for anything except application.Application # for toplevel widgets or with class different from wx... a class will be added if obj.IS_SLOT or obj.WX_CLASS=="spacer": if obj.WX_CLASS: # "slot" has no code generator, but "sizerslot" or "spacer" needs to be added self.add_object(obj) self.add_sizeritem(obj.parent_class_object, obj.parent, obj) return parent = obj.parent parent_class_object = obj.parent_class_object # used for adding to this object's sizer obj.IS_CLASS = IS_CLASS = obj.check_prop_truth("class") # first the item if IS_CLASS: self.add_class(obj) if not obj.IS_TOPLEVEL: added = self.add_object(obj) # added can be False if the widget is not supported else: added = False # then the children for child in obj.get_all_children(): assert obj.children.count(child)<=1 self._generate_code(None, None, None, child) # XRCCodeWriter does not use the other args # check whether the object belongs to some sizer; if applicable, add it to the sizer at the top of the stack if added and parent.IS_SIZER: if obj.WX_CLASS not in ("spacer",): # spacer and slot are adding itself to the sizer self.add_sizeritem(parent_class_object, parent, obj) def add_object(self, sub_obj): "Adds the object sub_obj to the XRC tree. The first argument is unused." # what we need in XRC is not top_obj, but sub_obj's true parent we don't need the sizer, but the window top_obj = sub_obj.parent_window builder = self.obj_builders.get( sub_obj.WX_CLASS, DefaultXrcObject ) try: # check whether we already created the xrc_obj xrc_obj = sub_obj.xrc except AttributeError: xrc_obj = builder(sub_obj) # builder functions must return a subclass of XrcObject sub_obj.xrc = xrc_obj else: # if we found it, remove it from the self.xrc_objects dictionary # (if it was there, i.e. the object is not a sizer), because this isn't a true toplevel object if sub_obj in self.xrc_objects: del self.xrc_objects[sub_obj] if not getattr(top_obj, "xrc"): # create XrcObject and store it in the self.xrc_objects dict top_xrc = self.obj_builders.get( top_obj.WX_CLASS, DefaultXrcObject )(top_obj) top_obj.xrc = top_xrc self.xrc_objects[top_obj] = top_xrc top_obj.xrc.children.append(xrc_obj) return True def add_sizeritem(self, unused, sizer, obj): "Adds a sizeritem to the XRC tree. The first argument is unused." # what we need in XRC is not toplevel, but sub_obj's true parent toplevel = obj.parent_window top_xrc = toplevel.xrc obj_xrc = obj.xrc try: sizer_xrc = sizer.xrc except AttributeError: # if the sizer has not an XrcObject yet, create it now sizer_xrc = self.obj_builders.get( sizer.WX_CLASS, DefaultXrcObject )(sizer) sizer.xrc = sizer_xrc # we now have to move the children from 'toplevel' to 'sizer' index = top_xrc.children.index(obj_xrc) if obj.WX_CLASS == 'spacer': sizer.xrc.children.append( SpacerXrcObject(obj) ) elif obj.WX_CLASS == 'sizerslot': if not sizer._IS_GRIDBAG: sizer.xrc.children.append( SpacerXrcObject(None) ) else: sizeritem_xrc = SizerItemXrcObject( obj_xrc, obj ) sizer.xrc.children.append(sizeritem_xrc) del top_xrc.children[index] def add_class(self, code_obj): """Add class behaves very differently for XRC output than for other languages (i.e. python): since custom classes are not supported in XRC, this has effect only for true toplevel widgets, i.e. frames and dialogs. For other kinds of widgets, this is equivalent to add_object""" if not code_obj in self.xrc_objects: builder = self.obj_builders.get( code_obj.WX_CLASS, DefaultXrcObject ) xrc_obj = builder(code_obj) code_obj.xrc = xrc_obj # add the xrc_obj to the dict of the toplevel ones self.xrc_objects[code_obj] = xrc_obj def generate_code_id(self, obj, id=None): return '', '' def _format_comment(self, msg): return '<!-- %s -->' % escape(msg.rstrip()) def _quote_str(self, s): return s writer = XRCCodeWriter() # The code writer is an instance of XRCCodeWriter language = writer.language # Language generated by this code generator