From: John Darrington Date: Tue, 10 Mar 2009 22:27:44 +0000 (+0900) Subject: Include gtk-builder-convert to process .glade files X-Git-Tag: v0.7.3~242 X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=493be05e2d65454fc7f14f417146049f56147709;p=pspp-builds.git Include gtk-builder-convert to process .glade files The gtk-builder-convert shipped with some older versions of gtk+ has proved problematic. This change includes a "known good" version, and builds using it. --- diff --git a/lib/gtk-contrib/automake.mk b/lib/gtk-contrib/automake.mk index c78e61f1..f3cd96e2 100644 --- a/lib/gtk-contrib/automake.mk +++ b/lib/gtk-contrib/automake.mk @@ -12,5 +12,6 @@ lib_gtk_contrib_libgtksheet_a_SOURCES = \ lib/gtk-contrib/gtkxpaned.h EXTRA_DIST += lib/gtk-contrib/OChangeLog \ - lib/gtk-contrib/README + lib/gtk-contrib/README \ + lib/gtk-contrib/gtk-builder-convert diff --git a/lib/gtk-contrib/gtk-builder-convert b/lib/gtk-contrib/gtk-builder-convert new file mode 100755 index 00000000..bfcb03c9 --- /dev/null +++ b/lib/gtk-contrib/gtk-builder-convert @@ -0,0 +1,750 @@ +#!/usr/bin/env python +# +# This file was downloaded from +# http://svn.gnome.org/svn/gtk+/tags/GTK_2_14_7/gtk/gtk-builder-convert +# on 11 March 2009 +# +# Copyright (C) 2006-2008 Async Open Source +# Henrique Romano +# Johan Dahlin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# TODO: +# Toolbars + +"""Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT] +Converts Glade files into XML files which can be loaded with GtkBuilder. +The [INPUT] file is + + -w, --skip-windows Convert everything but GtkWindow subclasses. + -r, --root Convert only widget named root and its children + -h, --help display this help and exit + +When OUTPUT is -, write to standard input. + +Examples: + gtk-builder-convert preference.glade preferences.ui + +Report bugs to http://bugzilla.gnome.org/.""" + +import getopt +import os +import sys + +from xml.dom import minidom, Node + +WINDOWS = ['GtkWindow', + 'GtkDialog', + 'GtkFileChooserDialog', + 'GtkMessageDialog'] + +# The subprocess is only available in Python 2.4+ +try: + import subprocess + subprocess # pyflakes +except ImportError: + subprocess = None + +def get_child_nodes(node): + assert node.tagName == 'object' + nodes = [] + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName != 'child': + continue + nodes.append(child) + return nodes + +def get_properties(node): + assert node.tagName == 'object' + properties = {} + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName != 'property': + continue + value = child.childNodes[0].data + properties[child.getAttribute('name')] = value + return properties + +def get_property(node, property_name): + assert node.tagName == 'object' + properties = get_properties(node) + return properties.get(property_name) + +def get_property_node(node, property_name): + assert node.tagName == 'object' + properties = {} + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName != 'property': + continue + if child.getAttribute('name') == property_name: + return child + +def get_signal_nodes(node): + assert node.tagName == 'object' + signals = [] + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName == 'signal': + signals.append(child) + return signals + +def get_property_nodes(node): + assert node.tagName == 'object' + properties = [] + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + # FIXME: handle comments + if child.tagName == 'property': + properties.append(child) + return properties + +def get_accelerator_nodes(node): + assert node.tagName == 'object' + accelerators = [] + for child in node.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName == 'accelerator': + accelerators.append(child) + return accelerators + +def get_object_node(child_node): + assert child_node.tagName == 'child', child_node + nodes = [] + for node in child_node.childNodes: + if node.nodeType != Node.ELEMENT_NODE: + continue + if node.tagName == 'object': + nodes.append(node) + assert len(nodes) == 1, nodes + return nodes[0] + +def copy_properties(node, props, prop_dict): + assert node.tagName == 'object' + for prop_name in props: + child = get_property_node(node, prop_name) + if child is not None: + prop_dict[prop_name] = child + + return node + +class GtkBuilderConverter(object): + + def __init__(self, skip_windows, root): + self.skip_windows = skip_windows + self.root = root + self.root_objects = [] + self.objects = {} + + # + # Public API + # + + def parse_file(self, file): + self._dom = minidom.parse(file) + self._parse() + + def parse_buffer(self, buffer): + self._dom = minidom.parseString(buffer) + self._parse() + + def to_xml(self): + xml = self._dom.toprettyxml("", "") + return xml.encode('utf-8') + + # + # Private + # + + def _get_object(self, name): + return self.objects.get(name) + + def _get_objects_by_attr(self, attribute, value): + return [w for w in self._dom.getElementsByTagName("object") + if w.getAttribute(attribute) == value] + + def _create_object(self, obj_class, obj_id, template=None, properties=None): + """ + Creates a new tag. + Optionally a name template can be provided which will be used + to avoid naming collisions. + The properties dictionary can either contain string values or Node + values. If a node is provided the name of the node will be overridden + by the dictionary key. + + @param obj_class: class of the object (class tag) + @param obj_id: identifier of the object (id tag) + @param template: name template to use, for example 'button' + @param properties: dictionary of properties + @type properties: string or Node. + @returns: Newly created node of the object + """ + if template is not None: + count = 1 + while True: + obj_id = template + str(count) + widget = self._get_object(obj_id) + if widget is None: + break + + count += 1 + + obj = self._dom.createElement('object') + obj.setAttribute('class', obj_class) + obj.setAttribute('id', obj_id) + if properties: + for name, value in properties.items(): + if isinstance(value, Node): + # Reuse the node, so translatable and context still will be + # set when converting nodes. See also #509153 + prop = value + else: + prop = self._dom.createElement('property') + prop.appendChild(self._dom.createTextNode(value)) + + prop.setAttribute('name', str(name)) + obj.appendChild(prop) + self.objects[obj_id] = obj + return obj + + def _create_root_object(self, obj_class, template, properties=None): + obj = self._create_object(obj_class, None, template, properties) + self.root_objects.append(obj) + return obj + + def _parse(self): + glade_iface = self._dom.getElementsByTagName("glade-interface") + assert glade_iface, ("Badly formed XML, there is " + "no tag.") + # Rename glade-interface to interface + glade_iface[0].tagName = 'interface' + self._interface = glade_iface[0] + + # Remove glade-interface doc type + for node in self._dom.childNodes: + if node.nodeType == Node.DOCUMENT_TYPE_NODE: + if node.name == 'glade-interface': + self._dom.removeChild(node) + + # Strip unsupported tags + for tag in ['requires', 'requires-version']: + for child in self._dom.getElementsByTagName(tag): + child.parentNode.removeChild(child) + + if self.root: + self._strip_root(self.root) + + # Rename widget to object + objects = self._dom.getElementsByTagName("widget") + for node in objects: + node.tagName = "object" + + for node in objects: + self._convert(node.getAttribute("class"), node) + self.objects[node.getAttribute('id')] = node + + # Convert Gazpachos UI tag + for node in self._dom.getElementsByTagName("ui"): + self._convert_ui(node) + + # Convert accessibility tag + for node in self._dom.getElementsByTagName("accessibility"): + self._convert_accessibility(node) + + # Output the newly created root objects and sort them + # by attribute id + for obj in sorted(self.root_objects, + key=lambda n: n.getAttribute('id'), + reverse=True): + self._interface.childNodes.insert(0, obj) + + def _convert(self, klass, node): + if klass == 'GtkNotebook': + self._packing_prop_to_child_attr(node, "type", "tab") + elif klass in ['GtkExpander', 'GtkFrame']: + self._packing_prop_to_child_attr( + node, "type", "label_item", "label") + elif klass == "GtkMenuBar": + self._convert_menu(node) + elif klass == "GtkMenu": + # Only convert toplevel popups + if node.parentNode == self._interface: + self._convert_menu(node, popup=True) + elif klass in WINDOWS and self.skip_windows: + self._remove_window(node) + self._default_widget_converter(node) + + def _default_widget_converter(self, node): + klass = node.getAttribute("class") + for prop in get_property_nodes(node): + prop_name = prop.getAttribute("name") + if prop_name == "sizegroup": + self._convert_sizegroup(node, prop) + elif prop_name == "tooltip" and klass != "GtkAction": + prop.setAttribute("name", "tooltip-text") + elif prop_name in ["response_id", 'response-id']: + # It does not make sense to convert responses when + # we're not going to output dialogs + if self.skip_windows: + continue + object_id = node.getAttribute('id') + response = prop.childNodes[0].data + self._convert_dialog_response(node, object_id, response) + prop.parentNode.removeChild(prop) + elif prop_name == "adjustment": + self._convert_adjustment(prop) + elif prop_name == "items" and klass in ['GtkComboBox', + 'GtkComboBoxEntry']: + self._convert_combobox_items(node, prop) + elif prop_name == "text" and klass == 'GtkTextView': + self._convert_textview_text(prop) + + def _remove_window(self, node): + object_node = get_object_node(get_child_nodes(node)[0]) + parent = node.parentNode + parent.removeChild(node) + parent.appendChild(object_node) + + def _convert_menu(self, node, popup=False): + if node.hasAttribute('constructor'): + return + + uimgr = self._create_root_object('GtkUIManager', + template='uimanager') + + if popup: + name = 'popup' + else: + name = 'menubar' + + menu = self._dom.createElement(name) + menu.setAttribute('name', node.getAttribute('id')) + node.setAttribute('constructor', uimgr.getAttribute('id')) + + for child in get_child_nodes(node): + obj_node = get_object_node(child) + item = self._convert_menuitem(uimgr, obj_node) + menu.appendChild(item) + child.removeChild(obj_node) + child.parentNode.removeChild(child) + + ui = self._dom.createElement('ui') + uimgr.appendChild(ui) + + ui.appendChild(menu) + + def _convert_menuitem(self, uimgr, obj_node): + children = get_child_nodes(obj_node) + name = 'menuitem' + if children: + child_node = children[0] + menu_node = get_object_node(child_node) + # Can be GtkImage, which will take care of later. + if menu_node.getAttribute('class') == 'GtkMenu': + name = 'menu' + + object_class = obj_node.getAttribute('class') + if object_class in ['GtkMenuItem', + 'GtkImageMenuItem', + 'GtkCheckMenuItem', + 'GtkRadioMenuItem']: + menu = self._dom.createElement(name) + elif object_class == 'GtkSeparatorMenuItem': + return self._dom.createElement('separator') + else: + raise NotImplementedError(object_class) + + menu.setAttribute('action', obj_node.getAttribute('id')) + self._add_action_from_menuitem(uimgr, obj_node) + if children: + for child in get_child_nodes(menu_node): + obj_node = get_object_node(child) + item = self._convert_menuitem(uimgr, obj_node) + menu.appendChild(item) + child.removeChild(obj_node) + child.parentNode.removeChild(child) + return menu + + def _menuitem_to_action(self, node, properties): + copy_properties(node, ['label', 'tooltip'], properties) + + def _togglemenuitem_to_action(self, node, properties): + self._menuitem_to_action(node, properties) + copy_properties(node, ['active'], properties) + + def _radiomenuitem_to_action(self, node, properties): + self._togglemenuitem_to_action(node, properties) + copy_properties(node, ['group'], properties) + + def _add_action_from_menuitem(self, uimgr, node): + properties = {} + object_class = node.getAttribute('class') + object_id = node.getAttribute('id') + if object_class == 'GtkMenuItem': + name = 'GtkAction' + self._menuitem_to_action(node, properties) + elif object_class == 'GtkCheckMenuItem': + name = 'GtkToggleAction' + self._togglemenuitem_to_action(node, properties) + elif object_class == 'GtkRadioMenuItem': + name = 'GtkRadioAction' + self._radiomenuitem_to_action(node, properties) + elif object_class == 'GtkImageMenuItem': + name = 'GtkAction' + children = get_child_nodes(node) + if (children and + children[0].getAttribute('internal-child') == 'image'): + image = get_object_node(children[0]) + child = get_property_node(image, 'stock') + if child is not None: + properties['stock_id'] = child + self._menuitem_to_action(node, properties) + elif object_class == 'GtkSeparatorMenuItem': + return + else: + raise NotImplementedError(object_class) + + if get_property(node, 'use_stock') == 'True': + if 'label' in properties: + properties['stock_id'] = properties['label'] + del properties['label'] + + properties['name'] = object_id + action = self._create_object(name, + object_id, + properties=properties) + for signal in get_signal_nodes(node): + signal_name = signal.getAttribute('name') + if signal_name in ['activate', 'toggled']: + action.appendChild(signal) + else: + print 'Unhandled signal %s::%s' % (node.getAttribute('class'), + signal_name) + + if not uimgr.childNodes: + child = self._dom.createElement('child') + uimgr.appendChild(child) + + group = self._create_object('GtkActionGroup', None, + template='actiongroup') + child.appendChild(group) + else: + group = uimgr.childNodes[0].childNodes[0] + + child = self._dom.createElement('child') + group.appendChild(child) + child.appendChild(action) + + for accelerator in get_accelerator_nodes(node): + signal_name = accelerator.getAttribute('signal') + if signal_name != 'activate': + print 'Unhandled accelerator signal for %s::%s' % ( + node.getAttribute('class'), signal_name) + continue + accelerator.removeAttribute('signal') + child.appendChild(accelerator) + + def _convert_sizegroup(self, node, prop): + # This is Gazpacho only + node.removeChild(prop) + obj = self._get_object(prop.childNodes[0].data) + if obj is None: + widgets = self._get_objects_by_attr("class", "GtkSizeGroup") + if widgets: + obj = widgets[-1] + else: + obj = self._create_root_object('GtkSizeGroup', + template='sizegroup') + + widgets = obj.getElementsByTagName("widgets") + if widgets: + assert len(widgets) == 1 + widgets = widgets[0] + else: + widgets = self._dom.createElement("widgets") + obj.appendChild(widgets) + + member = self._dom.createElement("widget") + member.setAttribute("name", node.getAttribute("id")) + widgets.appendChild(member) + + def _convert_dialog_response(self, node, object_name, response): + # 1) Get parent dialog node + while True: + # If we can't find the parent dialog, give up + if node == self._dom: + return + + if (node.tagName == 'object' and + node.getAttribute('class') == 'GtkDialog'): + dialog = node + break + node = node.parentNode + assert node + + # 2) Get dialogs action-widgets tag, create if not found + for child in dialog.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue + if child.tagName == 'action-widgets': + actions = child + break + else: + actions = self._dom.createElement("action-widgets") + dialog.appendChild(actions) + + # 3) Add action-widget tag for the response + action = self._dom.createElement("action-widget") + action.setAttribute("response", response) + action.appendChild(self._dom.createTextNode(object_name)) + actions.appendChild(action) + + def _convert_adjustment(self, prop): + properties = {} + if prop.childNodes: + data = prop.childNodes[0].data + value, lower, upper, step, page, page_size = data.split(' ') + properties.update(value=value, + lower=lower, + upper=upper, + step_increment=step, + page_increment=page, + page_size=page_size) + else: + prop.appendChild(self._dom.createTextNode("")) + + adj = self._create_root_object("GtkAdjustment", + template='adjustment', + properties=properties) + prop.childNodes[0].data = adj.getAttribute('id') + + def _convert_combobox_items(self, node, prop): + parent = prop.parentNode + if not prop.childNodes: + parent.removeChild(prop) + return + value = prop.childNodes[0].data + model = self._create_root_object("GtkListStore", + template="model") + + columns = self._dom.createElement('columns') + model.appendChild(columns) + + column = self._dom.createElement('column') + column.setAttribute('type', 'gchararray') + columns.appendChild(column) + + data = self._dom.createElement('data') + model.appendChild(data) + + for item in value.split('\n'): + row = self._dom.createElement('row') + data.appendChild(row) + + col = self._dom.createElement('col') + col.setAttribute('id', '0') + col.appendChild(self._dom.createTextNode(item)) + row.appendChild(col) + + model_prop = self._dom.createElement('property') + model_prop.setAttribute('name', 'model') + model_prop.appendChild( + self._dom.createTextNode(model.getAttribute('id'))) + parent.appendChild(model_prop) + + parent.removeChild(prop) + + child = self._dom.createElement('child') + node.appendChild(child) + cell_renderer = self._create_object('GtkCellRendererText', None, + template='renderer') + child.appendChild(cell_renderer) + + attributes = self._dom.createElement('attributes') + child.appendChild(attributes) + + attribute = self._dom.createElement('attribute') + attributes.appendChild(attribute) + attribute.setAttribute('name', 'text') + attribute.appendChild(self._dom.createTextNode('0')) + + def _convert_textview_text(self, prop): + if not prop.childNodes: + prop.parentNode.removeChild(prop) + return + + data = prop.childNodes[0].data + if prop.hasAttribute('translatable'): + prop.removeAttribute('translatable') + tbuffer = self._create_root_object("GtkTextBuffer", + template='textbuffer', + properties=dict(text=data)) + prop.childNodes[0].data = tbuffer.getAttribute('id') + prop.setAttribute('name', 'buffer') + + def _packing_prop_to_child_attr(self, node, prop_name, prop_val, + attr_val=None): + for child in get_child_nodes(node): + packing_props = [p for p in child.childNodes if p.nodeName == "packing"] + if not packing_props: + continue + assert len(packing_props) == 1 + packing_prop = packing_props[0] + properties = packing_prop.getElementsByTagName("property") + for prop in properties: + if (prop.getAttribute("name") != prop_name or + prop.childNodes[0].data != prop_val): + continue + packing_prop.removeChild(prop) + child.setAttribute(prop_name, attr_val or prop_val) + if len(properties) == 1: + child.removeChild(packing_prop) + + def _convert_ui(self, node): + cdata = node.childNodes[0] + data = cdata.toxml().strip() + if not data.startswith(""): + return + data = data[9:-3] + child = minidom.parseString(data).childNodes[0] + nodes = child.childNodes[:] + for child_node in nodes: + node.appendChild(child_node) + node.removeChild(cdata) + if not node.hasAttribute("id"): + return + + # Updating references made by widgets + parent_id = node.parentNode.getAttribute("id") + for widget in self._get_objects_by_attr("constructor", + node.getAttribute("id")): + widget.getAttributeNode("constructor").value = parent_id + node.removeAttribute("id") + + def _convert_accessibility(self, node): + objectNode = node.parentNode + parent_id = objectNode.getAttribute("id") + + properties = {} + for node in node.childNodes: + if node.nodeName == 'atkproperty': + node.tagName = 'property' + properties[node.getAttribute('name')] = node + node.parentNode.removeChild(node) + elif node.nodeName == 'atkrelation': + node.tagName = 'relation' + relation_type = node.getAttribute('type') + relation_type = relation_type.replace('_', '-') + node.setAttribute('type', relation_type) + elif node.nodeName == 'atkaction': + node.tagName = 'action' + + if properties: + child = self._dom.createElement('child') + child.setAttribute("internal-child", "accessible") + + atkobject = self._create_object( + "AtkObject", None, + template='a11y-%s' % (parent_id,), + properties=properties) + child.appendChild(atkobject) + objectNode.appendChild(child) + + def _strip_root(self, root_name): + for widget in self._dom.getElementsByTagName("widget"): + if widget.getAttribute('id') == root_name: + break + else: + raise SystemExit("Could not find an object called `%s'" % ( + root_name)) + + for child in self._interface.childNodes[:]: + if child.nodeType != Node.ELEMENT_NODE: + continue + child.parentNode.removeChild(child) + + self._interface.appendChild(widget) + + +def _indent(output): + if not subprocess: + return output + + for directory in os.environ['PATH'].split(os.pathsep): + filename = os.path.join(directory, 'xmllint') + if os.path.exists(filename): + break + else: + return output + + s = subprocess.Popen([filename, '--format', '-'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + s.stdin.write(output) + s.stdin.close() + return s.stdout.read() + +def usage(): + print __doc__ + +def main(args): + try: + opts, args = getopt.getopt(args[1:], "hwr:", + ["help", "skip-windows", "root="]) + except getopt.GetoptError: + usage() + return 2 + + if len(args) != 2: + usage() + return 2 + + input_filename, output_filename = args + + skip_windows = False + split = False + root = None + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-r", "--root"): + root = a + elif o in ("-w", "--skip-windows"): + skip_windows = True + + conv = GtkBuilderConverter(skip_windows=skip_windows, + root=root) + conv.parse_file(input_filename) + + xml = _indent(conv.to_xml()) + if output_filename == "-": + print xml + else: + open(output_filename, 'w').write(xml) + print "Wrote", output_filename + + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index 838a773a..9246b79f 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -249,7 +249,7 @@ src/ui/gui/psppire-marshal.h: src/ui/gui/marshaller-list glib-genmarshal --header --prefix=psppire_marshal $< > $@ .glade.ui: - gtk-builder-convert $< $@ + $(top_srcdir)/lib/gtk-contrib/gtk-builder-convert $< $@ desktopdir = $(datadir)/applications desktop_DATA = src/ui/gui/pspp.desktop