#!/usr/bin/python # encoding: UTF-8 # api: python # title: glade compiler for python # description: simplistic .glade to .python tranformation, generates flat gtk.Widget creation calls # comment: I can't believe I'm doing this... # type: filter # version: 2.0 # license: public domain # # Transforms a .glade file into a list of python gtk.Widget creation calls. # # Syntax: # glc2 project.glade >> project-gui.py # # Retains the glade widget name= IDs. # Does no indendation of itself - use the editor, Luke! # # The generated code expects a pre-defined signal[] dictionary, for binding gtk # signals to callback functions. # # Basic widgets work, even menus. gtk.Table layout is broken. # # There are couple of rulesets in here, which allow for easy extension to other # widget types. The packing_handlers should probably become a class, as it gets # a bit unreadable here. # # import sys import os import re from xml.dom import minidom from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities import gtk import pygtk from random import randint ELEMENT_NODE = 1 TEXT_NODE = 3 #wTree_prefix = "wTree." # this comes down to personal preferences, wTree_prefix = "" # object IDs can just be local variables, #wTree_prefix = "self." # go in a wTree, or directly into local object # looks for child tags in the current level only def subnodes(w, tag="child"): return [cn for cn in w.childNodes if (cn.nodeType==ELEMENT_NODE) and (cn.tagName in (tag))] # shortcut to text content def text(w): return "".join([t.data for t in w.childNodes if t.nodeType==TEXT_NODE]) # extracts s associated to a -tree (the packing is always sibling to the widget node) def packing_properties(next): pack = {} for p in subnodes(next,"packing"): for prop in subnodes(p, "property"): pack[prop.getAttribute("name")] = text(prop) return pack # pygtk naming def gtkclass(cls): return re.sub("Gtk", "gtk.", cls) def gtkconstant(con): if con == "True" or con == "False": return con elif re.match("^(\d+\.)?\d+$", con): return con elif re.match("GTK_", con): return re.sub("GTK_", "gtk.", con) elif "\n" in con: return '"""' + con + '"""' else: return '"' + con + '"' def gtkid(id): return re.sub("-", "_", id) # packing options def dict2params(prop, defaults=None): if not defaults: defaults = prop r = [] for name,default in defaults.iteritems(): value = str(prop.get(name, default)) if len(value): r.append(name+"="+value) return ", ".join(r) #-- rulesets rule! # special handling of .pack() and .add() functions for fastidious container widgets packing_handlers = { "GtkHPaned": lambda id, inum, sub_id, pack: (id + ".pack" + str(inum+1) + "(" + sub_id + ", resize=" + pack["resize"] + ", shrink=" + pack["shrink"] + ")"), "GtkVBox": lambda id, inum, sub_id, pack: (id + ".pack_start(" + sub_id + ", " + dict2params(pack, dict(expand=True, fill=True)) + ")"), "GtkHBox": lambda id, inum, sub_id, pack: (id + ".pack_start(" + sub_id + ", " + dict2params(pack, dict(expand=True, fill=True)) + ")"), "GtkNotebook": lambda id, inum, sub_id, pack: (pack.get("type")=="tab" and id+".append_page("+pack["last_id"]+", "+sub_id+")#"+str(pack) ), "GtkMenuItem": lambda id, inum, sub_id, pack: (("item" in id) and id+".set_submenu("+sub_id+")" or id+".add("+sub_id+")"), "GtkImageMenuItem": lambda id, inum, sub_id, pack: (("item" in id) and id+".set_submenu("+sub_id+")" or id+".add("+sub_id+")"), "GtkImageMenuItem>GtkImage": lambda id, inum, sub_id, pack: (id+".set_image("+sub_id+")"), "GtkTable": lambda id, inum, sub_id, pack: # this doesn't work - needs more sophisticated code, maybe counting rows and columns itself, ... (pack.get("top_attach") and (id+".attach("+sub_id+", "+dict2params(pack, dict(left_attach=0, right_attach=1, top_attach=-1, bottom_attach=-1))+")") or (id+".add("+sub_id+")")), "GtkLayout": lambda id, inum, sub_id, pack: (id+".put("+sub_id+", "+dict2params(pack, dict(x=0, y=0))+")"), "GtkFixed": lambda id, inum, sub_id, pack: (id+".put("+sub_id+", "+dict2params(pack, dict(x=0, y=0))+")"), "default": lambda id, inum, sub_id, pack: (id+".add("+sub_id+")"), } # additional methods for some gtk objects widget_extra_functions = { "GtkWindow": lambda w, id, prop: id+".show()", "GtkEntry": lambda w, id, prop: "#"+id+".set_text(_(...))", } # some options cannot be applied by .set_properties() and need special attention special_properties = { "tooltip": lambda cls, id, name, value: id+".set_tooltip_text("+value+")", "pixbuf": lambda cls, id, name, value: id+".set_property(\"pixbuf\", gtk.gdk.pixbuf_new_from_file("+value+"))", } #-- recurse through widgets # # This is the main function. It gets a DOM node "w" and extracts parameters # and childnodes from there. # For each widget, it searches the properties and signals, outputs them as pygtk calls. # It then goes on to look for elements, and recursively calls them. # On return the child widgets get attached to the current widget id. Usually a .add() # suffices, but some containers require packing_handlers{}, see above. # # Each widget() call returns the childs id and gtk class name as tuple. It's required # for packing subwidgets into their container. # # def widget(w, parent_cls="-", parent_prop={}): # basic widget attributes cls = w.getAttribute("class") obj = gtkclass(cls) id = wTree_prefix + gtkid(w.getAttribute("id")) # found a instead of widget if not cls and not id: cls = "PlaceHolder" id = "placeholder" + str(randint(9,999999)) obj = "gtk.Label" # simplest workaround # get properties properties = {} for prop in subnodes(w, "property"): properties[prop.getAttribute("name")] = gtkconstant(text(prop)) #-- write instantiation call # "gladename = gtk.Widget()" # print "\n#", cls, '"'+id+'"' #print "#properties =", properties init_params = properties_handling(cls, properties, "init") print id, "=", obj + "(" + init_params + ")" # write properties properties_handling(cls, properties, "remove") for prop_name,prop_value in properties.iteritems(): if (special_properties.has_key(prop_name)): output = special_properties[prop_name](cls, id, prop_name, prop_value) if output: print output continue # standard: print id + ".set_property(\"" + prop_name + "\", " + prop_value + ")" # write signals for sig in subnodes(w, "signal"): print id + ".connect(\"" + sig.getAttribute("name") + "\", signals.get(\"" + sig.getAttribute("handler") + "\", null_signal))" # child nodes childs = [] for next in subnodes(w, "child"): # sub widgets child_dom_node = subnodes(next, ("widget","or","placeholder"))[0] childs.append([ widget(w=child_dom_node, parent_cls=cls, parent_prop=properties), packing_properties(next) ]) # child packing last_id = "" for i,row in enumerate(childs): ((sub_id, child_cls), pack) = row pack["last_id"] = last_id # special widget groups if packing_handlers.has_key(cls + ">" + child_cls): print packing_handlers[cls + ">" + child_cls](id, i, sub_id, pack) # various .add/.pack_variations() elif packing_handlers.has_key(cls): print packing_handlers[cls](id, i, sub_id, pack) # unqualified packing else: print id+".add("+sub_id+")" last_id = sub_id # write extra widget features, if any if (widget_extra_functions.has_key(cls)): print widget_extra_functions[cls](w, id, properties) # return current id to parent (for .add/.pack) return (id, cls) #end widget() #-- some widget properties must be passed to the widget __init__ call, # though most of these incomprehensible rewrite rules are for MenuItems (very nuts!) def properties_handling(cls, properties, what="init"): when = { "init": { # these properties are supplied as named parameters at widget instantiation (and can be aliased) "GtkMenuItem": {"label":"label", "use_underline":"use_underline"}, "GtkImageMenuItem": {"stock_id":"stock_id", "label":"stock_id"}, }, "remove": { # Glade-3 adds some illegal properties, we remove them here "GtkImageMenuItem": {"use_underline":1, "use_stock":1, "label":1}, "GtkButton": {"response_id":1,}, "GtkCheckButton": {"response_id":1,}, } } # list of name=val pairs result = [] # walk through rulesets if (when[what].has_key(cls)): for prop_name,set_name in when[what][cls].iteritems(): if properties.has_key(prop_name): # add result.append(str(set_name) + "=" + properties[prop_name]) del properties[prop_name] # make sure it doesn't get reused for obj.set_properties() # done return ", ".join(result) #-- run if __name__ == "__main__": #-- open file dom = minidom.parse(sys.argv[1]) root = dom.getElementsByTagName("glade-interface")[0] # preface print "import pygtk, gtk, gtk.gdk" print "" print "#-- code generated by glc2, so sorry!" print "signals = {} # must associate signal names to actual callback functions" print "null_signal = lambda widget=None, event=None, extra=None: False" print (wTree_prefix,"#wTree")[not wTree_prefix or wTree_prefix=="self."], "= object() # some people seem to love wTree" print "" # start with root widget for windows in subnodes(root, "widget"): (id, ccls) = widget(windows) # display every found window widget print "" print id+".show()" print "\n\n\n#----------\n\n\n" # postface print "gtk.main()"