--- /dev/null
+#!/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 <henrique@async.com.br>
+# Johan Dahlin <jdahlin@async.com.br>
+#
+# 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 <object> 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 <glade-interface> 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("<![CDATA[") or not data.endswith("]]>"):
+ 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))