Пример #1
0
 def __init__(self, QtCoreModule, QtGuiModule, creatorPolicy):
     self.factory = QObjectCreator(creatorPolicy)
     self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule)
     
     global QtCore, QtGui
     QtCore = QtCoreModule
     QtGui = QtGuiModule
     
     self.reset()
Пример #2
0
 def __init__(self, QtCoreModule, QtGuiModule, creatorPolicy):
     self.factory = QObjectCreator(creatorPolicy)
     self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule)
     
     global QtCore, QtGui
     QtCore = QtCoreModule
     QtGui = QtGuiModule
     
     self.reset()
Пример #3
0
class UIParser(object):    
    def __init__(self, QtCoreModule, QtGuiModule, creatorPolicy):
        self.factory = QObjectCreator(creatorPolicy)
        self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule)
        
        global QtCore, QtGui
        QtCore = QtCoreModule
        QtGui = QtGuiModule
        
        self.reset()

    def uniqueName(self, name):
        """UIParser.uniqueName(string) -> string

        Create a unique name from a string.
        >>> p = UIParser(QtCore, QtGui)
        >>> p.uniqueName("foo")
        'foo'
        >>> p.uniqueName("foo")
        'foo1'
        """
        try:
            suffix = self.name_suffixes[name]
        except KeyError:
            self.name_suffixes[name] = 0
            return name

        suffix += 1
        self.name_suffixes[name] = suffix

        return "%s%i" % (name, suffix)

    def reset(self):
        try: self.wprops.reset()
        except AttributeError: pass
        self.toplevelWidget = None
        self.stack = WidgetStack()
        self.name_suffixes = {}
        self.defaults = {"spacing": 6, "margin": 0}
        self.actions = []
        self.currentActionGroup = None
        self.resources = []
        self.button_groups = []

    def setupObject(self, clsname, parent, branch, is_attribute = True):
        name = self.uniqueName(branch.attrib.get("name") or clsname[1:].lower())
        if parent is None:
            args = ()
        else:
            args = (parent, )
        obj =  self.factory.createQObject(clsname, name, args, is_attribute)
        self.wprops.setProperties(obj, branch)
        obj.setObjectName(name)
        if is_attribute:
            setattr(self.toplevelWidget, name, obj)
        return obj
    
    def createWidget(self, elem):
        def widgetClass(elem):
            cls = elem.attrib["class"].replace('::', '.')
            if cls == "Line":
                return "QFrame"
            else:
                return cls
            
        self.column_counter = 0
        self.row_counter = 0
        self.item_nr = 0
        self.itemstack = []
        self.sorting_enabled = None
        
        parent = self.stack.topwidget
        if isinstance(parent, (QtGui.QToolBox, QtGui.QTabWidget,
                               QtGui.QStackedWidget, QtGui.QDockWidget,
                               QtGui.QWizard)):
            parent = None
        
        
        self.stack.push(self.setupObject(widgetClass(elem), parent, elem))

        if isinstance(self.stack.topwidget, QtGui.QTableWidget):
            self.stack.topwidget.setColumnCount(len(elem.findall("column")))
            self.stack.topwidget.setRowCount(len(elem.findall("row")))

        self.traverseWidgetTree(elem)
        widget = self.stack.popWidget()

        if isinstance(widget, QtGui.QTreeView):
            self.handleHeaderView(elem, "header", widget.header())

        elif isinstance(widget, QtGui.QTableView):
            self.handleHeaderView(elem, "horizontalHeader",
                    widget.horizontalHeader())
            self.handleHeaderView(elem, "verticalHeader",
                    widget.verticalHeader())

        elif isinstance(widget, QtGui.QAbstractButton):
            bg_i18n = self.wprops.getAttribute(elem, "buttonGroup")
            if bg_i18n is not None:
                bg_name = bg_i18n.string

                for bg in self.button_groups:
                    if bg.objectName() == bg_name:
                        break
                else:
                    bg = self.factory.createQObject("QButtonGroup", bg_name,
                            (self.toplevelWidget, ))
                    bg.setObjectName(bg_name)
                    self.button_groups.append(bg)

                bg.addButton(widget)

        if self.sorting_enabled is not None:
            widget.setSortingEnabled(self.sorting_enabled)
            self.sorting_enabled = None
        
        if self.stack.topIsLayout():
            lay = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(lay, QtGui.QFormLayout):
                lay.setWidget(gp[0], self._form_layout_role(gp), widget)
            else:
                lay.addWidget(widget, *gp)

        topwidget = self.stack.topwidget

        if isinstance(topwidget, QtGui.QToolBox):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addItem(widget, icon, self.wprops.getAttribute(elem, "label"))
            else:
                topwidget.addItem(widget, self.wprops.getAttribute(elem, "label"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setItemToolTip(topwidget.indexOf(widget), tooltip)
                
        elif isinstance(topwidget, QtGui.QTabWidget):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addTab(widget, icon, self.wprops.getAttribute(elem, "title"))
            else:
                topwidget.addTab(widget, self.wprops.getAttribute(elem, "title"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setTabToolTip(topwidget.indexOf(widget), tooltip)
            
        elif isinstance(topwidget, QtGui.QWizard):
            topwidget.addPage(widget)
            
        elif isinstance(topwidget, QtGui.QStackedWidget):
            topwidget.addWidget(widget)
            
        elif isinstance(topwidget, (QtGui.QDockWidget, QtGui.QScrollArea)):
            topwidget.setWidget(widget)
            
        elif isinstance(topwidget, QtGui.QMainWindow):
            if type(widget) == QtGui.QWidget:
                topwidget.setCentralWidget(widget)
            elif isinstance(widget, QtGui.QToolBar):
                tbArea = self.wprops.getAttribute(elem, "toolBarArea")

                if tbArea is None:
                    topwidget.addToolBar(widget)
                else:
                    if isinstance(tbArea, str):
                        tbArea = getattr(QtCore.Qt, tbArea)
                    else:
                        tbArea = QtCore.Qt.ToolBarArea(tbArea)

                    topwidget.addToolBar(tbArea, widget)

                tbBreak = self.wprops.getAttribute(elem, "toolBarBreak")

                if tbBreak:
                    topwidget.insertToolBarBreak(widget)

            elif isinstance(widget, QtGui.QMenuBar):
                topwidget.setMenuBar(widget)
            elif isinstance(widget, QtGui.QStatusBar):
                topwidget.setStatusBar(widget)
            elif isinstance(widget, QtGui.QDockWidget):
                dwArea = self.wprops.getAttribute(elem, "dockWidgetArea")
                topwidget.addDockWidget(QtCore.Qt.DockWidgetArea(dwArea),
                        widget)

    def handleHeaderView(self, elem, name, header):
        value = self.wprops.getAttribute(elem, name + "Visible")
        if value is not None:
            header.setVisible(value)

        value = self.wprops.getAttribute(elem, name + "CascadingSectionResizes")
        if value is not None:
            header.setCascadingSectionResizes(value)

        value = self.wprops.getAttribute(elem, name + "DefaultSectionSize")
        if value is not None:
            header.setDefaultSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "HighlightSections")
        if value is not None:
            header.setHighlightSections(value)

        value = self.wprops.getAttribute(elem, name + "MinimumSectionSize")
        if value is not None:
            header.setMinimumSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "ShowSortIndicator")
        if value is not None:
            header.setSortIndicatorShown(value)

        value = self.wprops.getAttribute(elem, name + "StretchLastSection")
        if value is not None:
            header.setStretchLastSection(value)

    def createSpacer(self, elem):
        width = elem.findtext("property/size/width")
        height = elem.findtext("property/size/height")

        if width is None or height is None:
            size_args = ()
        else:
            size_args = (int(width), int(height))

        sizeType = self.wprops.getProperty(elem, "sizeType",
                QtGui.QSizePolicy.Expanding)

        policy = (QtGui.QSizePolicy.Minimum, sizeType)

        if self.wprops.getProperty(elem, "orientation") == QtCore.Qt.Horizontal:
            policy = policy[1], policy[0]

        spacer = self.factory.createQObject("QSpacerItem",
                self.uniqueName("spacerItem"), size_args + policy,
                is_attribute=False)

        if self.stack.topIsLayout():
            lay = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(lay, QtGui.QFormLayout):
                lay.setItem(gp[0], self._form_layout_role(gp), spacer)
            else:
                lay.addItem(spacer, *gp)

    def createLayout(self, elem):
        # Qt v4.3 introduced setContentsMargins() and separate values for each
        # of the four margins which are specified as separate properties.  This
        # doesn't really fit the way we parse the tree (why aren't the values
        # passed as attributes of a single property?) so we create a new
        # property and inject it.  However, if we find that they have all been
        # specified and have the same value then we inject a different property
        # that is compatible with older versions of Qt.
        left = self.wprops.getProperty(elem, 'leftMargin', -1)
        top = self.wprops.getProperty(elem, 'topMargin', -1)
        right = self.wprops.getProperty(elem, 'rightMargin', -1)
        bottom = self.wprops.getProperty(elem, 'bottomMargin', -1)

        # Count the number of properties and if they had the same value.
        def comp_property(m, so_far=-2, nr=0):
            if m >= 0:
                nr += 1

                if so_far == -2:
                    so_far = m
                elif so_far != m:
                    so_far = -1

            return so_far, nr

        margin, nr_margins = comp_property(left)
        margin, nr_margins = comp_property(top, margin, nr_margins)
        margin, nr_margins = comp_property(right, margin, nr_margins)
        margin, nr_margins = comp_property(bottom, margin, nr_margins)

        if nr_margins > 0:
            if nr_margins == 4 and margin >= 0:
                # We can inject the old margin property.
                me = SubElement(elem, 'property', name='margin')
                SubElement(me, 'number').text = str(margin)
            else:
                # We have to inject the new internal property.
                cme = SubElement(elem, 'property', name='pyuicContentsMargins')
                SubElement(cme, 'number').text = str(left)
                SubElement(cme, 'number').text = str(top)
                SubElement(cme, 'number').text = str(right)
                SubElement(cme, 'number').text = str(bottom)

        # We do the same for setHorizontalSpacing() and setVerticalSpacing().
        horiz = self.wprops.getProperty(elem, 'horizontalSpacing', -1)
        vert = self.wprops.getProperty(elem, 'verticalSpacing', -1)

        if horiz >= 0 or vert >= 0:
            # We inject the new internal property.
            cme = SubElement(elem, 'property', name='pyuicSpacing')
            SubElement(cme, 'number').text = str(horiz)
            SubElement(cme, 'number').text = str(vert)

        classname = elem.attrib["class"]
        if self.stack.topIsLayout():
            parent = None
        else:
            parent = self.stack.topwidget
        if "name" not in elem.attrib:
            elem.attrib["name"] = classname[1:].lower()
        self.stack.push(self.setupObject(classname, parent, elem))
        self.traverseWidgetTree(elem)

        layout = self.stack.popLayout()
        self.configureLayout(elem, layout)

        if self.stack.topIsLayout():
            top_layout = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(top_layout, QtGui.QFormLayout):
                top_layout.setLayout(gp[0], self._form_layout_role(gp), layout)
            else:
                top_layout.addLayout(layout, *gp)

    def configureLayout(self, elem, layout):
        if isinstance(layout, QtGui.QGridLayout):
            self.setArray(elem, 'columnminimumwidth',
                    layout.setColumnMinimumWidth)
            self.setArray(elem, 'rowminimumheight',
                    layout.setRowMinimumHeight)
            self.setArray(elem, 'columnstretch', layout.setColumnStretch)
            self.setArray(elem, 'rowstretch', layout.setRowStretch)

        elif isinstance(layout, QtGui.QBoxLayout):
            self.setArray(elem, 'stretch', layout.setStretch)

    def setArray(self, elem, name, setter):
        array = elem.attrib.get(name)
        if array:
            for idx, value in enumerate(array.split(',')):
                value = int(value)
                if value > 0:
                    setter(idx, value)

    def handleItem(self, elem):
        if self.stack.topIsLayout():
            elem[0].attrib["grid-position"] = gridPosition(elem)
            self.traverseWidgetTree(elem)
        else:
            w = self.stack.topwidget

            if isinstance(w, QtGui.QComboBox):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")

                if icon:
                    w.addItem(icon, '')
                else:
                    w.addItem('')

                w.setItemText(self.item_nr, text)

            elif isinstance(w, QtGui.QListWidget):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")
                flags = self.wprops.getProperty(elem, "flags")
                check_state = self.wprops.getProperty(elem, "checkState")
                background = self.wprops.getProperty(elem, "background")
                foreground = self.wprops.getProperty(elem, "foreground")

                if icon or flags or check_state:
                    item_name = "item"
                else:
                    item_name = None

                item = self.factory.createQObject("QListWidgetItem", item_name,
                        (w, ), False)

                if self.item_nr == 0:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                if text:
                    w.item(self.item_nr).setText(text)

                if icon:
                    item.setIcon(icon)

                if flags:
                    item.setFlags(flags)

                if check_state:
                    item.setCheckState(check_state)

                if background:
                    item.setBackground(background)

                if foreground:
                    item.setForeground(foreground)

            elif isinstance(w, QtGui.QTreeWidget):
                if self.itemstack:
                    parent, _ = self.itemstack[-1]
                    _, nr_in_root = self.itemstack[0]
                else:
                    parent = w
                    nr_in_root = self.item_nr

                item = self.factory.createQObject("QTreeWidgetItem",
                        "item_%d" % len(self.itemstack), (parent, ), False)

                if self.item_nr == 0 and not self.itemstack:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                self.itemstack.append((item, self.item_nr))
                self.item_nr = 0

                # We have to access the item via the tree when setting the
                # text.
                titm = w.topLevelItem(nr_in_root)
                for child, nr_in_parent in self.itemstack[1:]:
                    titm = titm.child(nr_in_parent)

                column = -1
                for prop in elem.findall("property"):
                    c_prop = self.wprops.convert(prop)
                    c_prop_name = prop.attrib["name"]

                    if c_prop_name == "text":
                        column += 1
                        if c_prop:
                            titm.setText(column, c_prop)
                    elif c_prop_name == "icon":
                        item.setIcon(column, c_prop)
                    elif c_prop_name == "flags":
                        item.setFlags(c_prop)
                    elif c_prop_name == "checkState":
                        item.setCheckState(column, c_prop)
                    elif c_prop_name == "background":
                        item.setBackground(column, c_prop)
                    elif c_prop_name == "foreground":
                        item.setForeground(column, c_prop)

                self.traverseWidgetTree(elem)
                _, self.item_nr = self.itemstack.pop()

            elif isinstance(w, QtGui.QTableWidget):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")
                flags = self.wprops.getProperty(elem, "flags")
                check_state = self.wprops.getProperty(elem, "checkState")
                background = self.wprops.getProperty(elem, "background")
                foreground = self.wprops.getProperty(elem, "foreground")

                item = self.factory.createQObject("QTableWidgetItem", "item",
                        (), False)

                if self.item_nr == 0:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                row = int(elem.attrib["row"])
                col = int(elem.attrib["column"])

                if icon:
                    item.setIcon(icon)

                if flags:
                    item.setFlags(flags)

                if check_state:
                    item.setCheckState(check_state)

                if background:
                    item.setBackground(background)

                if foreground:
                    item.setForeground(foreground)

                w.setItem(row, col, item)

                if text:
                    # Text is translated so we don't have access to the item
                    # attribute when generating code so we must get it from the
                    # widget after it has been set.
                    w.item(row, col).setText(text)

            self.item_nr += 1

    def addAction(self, elem):
        self.actions.append((self.stack.topwidget, elem.attrib["name"]))

    def addHeader(self, elem):
        w = self.stack.topwidget

        if isinstance(w, QtGui.QTreeWidget):
            text = self.wprops.getProperty(elem, "text")
            icon = self.wprops.getProperty(elem, "icon")

            if text:
                w.headerItem().setText(self.column_counter, text)

            if icon:
                w.headerItem().setIcon(self.column_counter, icon)

            self.column_counter += 1

        elif isinstance(w, QtGui.QTableWidget):
            if len(elem) == 0:
                return

            text = self.wprops.getProperty(elem, "text")
            icon = self.wprops.getProperty(elem, "icon")

            item = self.factory.createQObject("QTableWidgetItem", "item",
                        (), False)

            if elem.tag == "column":
                w.setHorizontalHeaderItem(self.column_counter, item)

                if text:
                    w.horizontalHeaderItem(self.column_counter).setText(text)

                if icon:
                    item.setIcon(icon)

                self.column_counter += 1
            elif elem.tag == "row":
                w.setVerticalHeaderItem(self.row_counter, item)

                if text:
                    w.verticalHeaderItem(self.row_counter).setText(text)

                if icon:
                    item.setIcon(icon)

                self.row_counter += 1

    def createAction(self, elem):
        self.setupObject("QAction", self.currentActionGroup or self.toplevelWidget,
                         elem)

    def createActionGroup(self, elem):
        action_group = self.setupObject("QActionGroup", self.toplevelWidget, elem)
        self.currentActionGroup = action_group
        self.traverseWidgetTree(elem)
        self.currentActionGroup = None

    widgetTreeItemHandlers = {
        "widget"    : createWidget,
        "addaction" : addAction,
        "layout"    : createLayout,
        "spacer"    : createSpacer,
        "item"      : handleItem,
        "action"    : createAction,
        "actiongroup": createActionGroup,
        "column"    : addHeader,
        "row"       : addHeader,
        }

    def traverseWidgetTree(self, elem):
        for child in iter(elem):
            try:
                handler = self.widgetTreeItemHandlers[child.tag]
            except KeyError: 
                continue

            handler(self, child)

    def createUserInterface(self, elem):
        # Get the names of the class and widget.
        cname = elem.attrib["class"]
        wname = elem.attrib["name"]

        # If there was no widget name then derive it from the class name.
        if not wname:
            wname = cname

            if wname.startswith("Q"):
                wname = wname[1:]

            wname = wname[0].lower() + wname[1:]

        self.toplevelWidget = self.createToplevelWidget(cname, wname)
        self.toplevelWidget.setObjectName(wname)
        DEBUG("toplevel widget is %s",
              self.toplevelWidget.metaObject().className())
        self.wprops.setProperties(self.toplevelWidget, elem)
        self.stack.push(self.toplevelWidget)
        self.traverseWidgetTree(elem)
        self.stack.popWidget()
        self.addActions()
        self.setBuddies()
        self.setDelayedProps()
        
    def addActions(self):
        for widget, action_name in self.actions:
            if action_name == "separator":
                widget.addSeparator()
            else:
                DEBUG("add action %s to %s", action_name, widget.objectName())
                action_obj = getattr(self.toplevelWidget, action_name)
                if isinstance(action_obj, QtGui.QMenu):
                    widget.addAction(action_obj.menuAction())
                elif not isinstance(action_obj, QtGui.QActionGroup):
                    widget.addAction(action_obj)

    def setDelayedProps(self):
        for func, args in self.wprops.delayed_props:
            func(args)
            
    def setBuddies(self):
        for widget, buddy in self.wprops.buddies:
            DEBUG("%s is buddy of %s", buddy, widget.objectName())
            try:
                widget.setBuddy(getattr(self.toplevelWidget, buddy))
            except AttributeError:
                DEBUG("ERROR in ui spec: %s (buddy of %s) does not exist",
                      buddy, widget.objectName())

    def classname(self, elem):
        DEBUG("uiname is %s", elem.text)
        name = elem.text

        if name is None:
            name = ""

        self.uiname = name
        self.wprops.uiname = name
        self.setContext(name)

    def setContext(self, context):
        """
        Reimplemented by a sub-class if it needs to know the translation
        context.
        """
        pass

    def readDefaults(self, elem):
        self.defaults["margin"] = int(elem.attrib["margin"])
        self.defaults["spacing"] = int(elem.attrib["spacing"])

    def setTaborder(self, elem):
        lastwidget = None
        for widget_elem in elem:
            widget = getattr(self.toplevelWidget, widget_elem.text)

            if lastwidget is not None:
                self.toplevelWidget.setTabOrder(lastwidget, widget)

            lastwidget = widget

    def readResources(self, elem):
        """
        Read a "resources" tag and add the module to import to the parser's
        list of them.
        """
        for include in elem.getiterator("include"):
            loc = include.attrib.get("location")

            # Assume our convention for naming the Python files generated by
            # pyrcc4.
            if loc and loc.endswith('.qrc'):
                self.resources.append(os.path.basename(loc[:-4] + '_rc'))

    def createConnections(self, elem):
        def name2object(obj):
            if obj == self.uiname:
                return self.toplevelWidget
            else:
                return getattr(self.toplevelWidget, obj)
        for conn in iter(elem):
            QtCore.QObject.connect(name2object(conn.findtext("sender")),
                                   QtCore.SIGNAL(conn.findtext("signal")),
                                   self.factory.getSlot(name2object(conn.findtext("receiver")),
                                                    conn.findtext("slot").split("(")[0]))
        QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget)

    def customWidgets(self, elem):
        def header2module(header):
            """header2module(header) -> string

            Convert paths to C++ header files to according Python modules
            >>> header2module("foo/bar/baz.h")
            'foo.bar.baz'
            """
            if header.endswith(".h"):
                header = header[:-2]

            mpath = []
            for part in header.split('/'):
                # Ignore any empty parts or those that refer to the current
                # directory.
                if part not in ('', '.'):
                    if part == '..':
                        # We should allow this for Python3.
                        raise SyntaxError("custom widget header file name may not contain '..'.")

                    mpath.append(part)

            return '.'.join(mpath)
    
        for custom_widget in iter(elem):
            classname = custom_widget.findtext("class")
            if classname.startswith("Q3"):
                raise NoSuchWidgetError(classname)
            self.factory.addCustomWidget(classname,
                                     custom_widget.findtext("extends") or "QWidget",
                                     header2module(custom_widget.findtext("header")))

    def createToplevelWidget(self, classname, widgetname):
        raise NotImplementedError
    
    # finalize will be called after the whole tree has been parsed and can be
    # overridden.
    def finalize(self):
        pass

    def parse(self, filename, base_dir=''):
        self.wprops.set_base_dir(base_dir)

        # The order in which the different branches are handled is important.
        # The widget tree handler relies on all custom widgets being known, and
        # in order to create the connections, all widgets have to be populated.
        branchHandlers = (
            ("layoutdefault", self.readDefaults),
            ("class",         self.classname),
            ("customwidgets", self.customWidgets),
            ("widget",        self.createUserInterface),
            ("connections",   self.createConnections),
            ("tabstops",      self.setTaborder),
            ("resources",     self.readResources),
        )

        document = parse(filename)
        version = document.getroot().attrib["version"]
        DEBUG("UI version is %s" % (version,))
        # Right now, only version 4.0 is supported.
        assert version in ("4.0",)
        for tagname, actor in branchHandlers:
            elem = document.find(tagname)
            if elem is not None:
                actor(elem)
        self.finalize()
        w = self.toplevelWidget
        self.reset()
        return w

    @staticmethod
    def _form_layout_role(grid_position):
        if grid_position[3] > 1:
            role = QtGui.QFormLayout.SpanningRole
        elif grid_position[1] == 0:
            role = QtGui.QFormLayout.FieldRole
        else:
            role = QtGui.QFormLayout.LabelRole

        return role
Пример #4
0
class UIParser(object):    
    def __init__(self, QtCoreModule, QtGuiModule, creatorPolicy):
        self.factory = QObjectCreator(creatorPolicy)
        self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule)
        
        global QtCore, QtGui
        QtCore = QtCoreModule
        QtGui = QtGuiModule
        
        self.reset()

    def uniqueName(self, name):
        """UIParser.uniqueName(string) -> string

        Create a unique name from a string.
        >>> p = UIParser(QtCore, QtGui)
        >>> p.uniqueName("foo")
        'foo'
        >>> p.uniqueName("foo")
        'foo1'
        """
        try:
            suffix = self.name_suffixes[name]
        except KeyError:
            self.name_suffixes[name] = 0
            return name

        suffix += 1
        self.name_suffixes[name] = suffix

        return "%s%i" % (name, suffix)

    def reset(self):
        try: self.wprops.reset()
        except AttributeError: pass
        self.toplevelWidget = None
        self.stack = WidgetStack()
        self.name_suffixes = {}
        self.defaults = {'spacing': -1, 'margin': -1}
        self.actions = []
        self.currentActionGroup = None
        self.resources = []
        self.button_groups = {}

    def setupObject(self, clsname, parent, branch, is_attribute=True):
        name = self.uniqueName(branch.attrib.get('name') or clsname[1:].lower())

        if parent is None:
            args = ()
        else:
            args = (parent, )

        obj = self.factory.createQObject(clsname, name, args, is_attribute)

        self.wprops.setProperties(obj, branch)
        obj.setObjectName(name)

        if is_attribute:
            setattr(self.toplevelWidget, name, obj)

        return obj

    def getProperty(self, elem, name):
        for prop in elem.findall('property'):
            if prop.attrib['name'] == name:
                return prop

        return None

    def createWidget(self, elem):
        self.column_counter = 0
        self.row_counter = 0
        self.item_nr = 0
        self.itemstack = []
        self.sorting_enabled = None

        widget_class = elem.attrib['class'].replace('::', '.')
        if widget_class == 'Line':
            widget_class = 'QFrame'
        
        # Ignore the parent if it is a container.
        parent = self.stack.topwidget
        if isinstance(parent, (QtGui.QDockWidget, QtGui.QMdiArea,
                               QtGui.QScrollArea, QtGui.QStackedWidget,
                               QtGui.QToolBox, QtGui.QTabWidget,
                               QtGui.QWizard)):
            parent = None

        self.stack.push(self.setupObject(widget_class, parent, elem))

        if isinstance(self.stack.topwidget, QtGui.QTableWidget):
            if self.getProperty(elem, 'columnCount') is None:
                self.stack.topwidget.setColumnCount(len(elem.findall("column")))

            if self.getProperty(elem, 'rowCount') is None:
                self.stack.topwidget.setRowCount(len(elem.findall("row")))

        self.traverseWidgetTree(elem)
        widget = self.stack.popWidget()

        if isinstance(widget, QtGui.QTreeView):
            self.handleHeaderView(elem, "header", widget.header())

        elif isinstance(widget, QtGui.QTableView):
            self.handleHeaderView(elem, "horizontalHeader",
                    widget.horizontalHeader())
            self.handleHeaderView(elem, "verticalHeader",
                    widget.verticalHeader())

        elif isinstance(widget, QtGui.QAbstractButton):
            bg_i18n = self.wprops.getAttribute(elem, "buttonGroup")
            if bg_i18n is not None:
                # This should be handled properly in case the problem arises
                # elsewhere as well.
                try:
                    # We are compiling the .ui file.
                    bg_name = bg_i18n.string
                except AttributeError:
                    # We are loading the .ui file.
                    bg_name = bg_i18n

                bg = self.button_groups[bg_name]

                if bg.object is None:
                    bg.object = self.factory.createQObject("QButtonGroup",
                            bg_name, (self.toplevelWidget, ))
                    setattr(self.toplevelWidget, bg_name, bg.object)

                    bg.object.setObjectName(bg_name)

                    if not bg.exclusive:
                        bg.object.setExclusive(False)

                bg.object.addButton(widget)

        if self.sorting_enabled is not None:
            widget.setSortingEnabled(self.sorting_enabled)
            self.sorting_enabled = None
        
        if self.stack.topIsLayout():
            lay = self.stack.peek()
            lp = elem.attrib['layout-position']

            if isinstance(lay, QtGui.QFormLayout):
                lay.setWidget(lp[0], self._form_layout_role(lp), widget)
            else:
                lay.addWidget(widget, *lp)

        topwidget = self.stack.topwidget

        if isinstance(topwidget, QtGui.QToolBox):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addItem(widget, icon, self.wprops.getAttribute(elem, "label"))
            else:
                topwidget.addItem(widget, self.wprops.getAttribute(elem, "label"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setItemToolTip(topwidget.indexOf(widget), tooltip)
                
        elif isinstance(topwidget, QtGui.QTabWidget):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addTab(widget, icon, self.wprops.getAttribute(elem, "title"))
            else:
                topwidget.addTab(widget, self.wprops.getAttribute(elem, "title"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setTabToolTip(topwidget.indexOf(widget), tooltip)
            
        elif isinstance(topwidget, QtGui.QWizard):
            topwidget.addPage(widget)
            
        elif isinstance(topwidget, QtGui.QStackedWidget):
            topwidget.addWidget(widget)
            
        elif isinstance(topwidget, (QtGui.QDockWidget, QtGui.QScrollArea)):
            topwidget.setWidget(widget)
            
        elif isinstance(topwidget, QtGui.QMainWindow):
            if type(widget) == QtGui.QWidget:
                topwidget.setCentralWidget(widget)
            elif isinstance(widget, QtGui.QToolBar):
                tbArea = self.wprops.getAttribute(elem, "toolBarArea")

                if tbArea is None:
                    topwidget.addToolBar(widget)
                else:
                    topwidget.addToolBar(tbArea, widget)

                tbBreak = self.wprops.getAttribute(elem, "toolBarBreak")

                if tbBreak:
                    topwidget.insertToolBarBreak(widget)

            elif isinstance(widget, QtGui.QMenuBar):
                topwidget.setMenuBar(widget)
            elif isinstance(widget, QtGui.QStatusBar):
                topwidget.setStatusBar(widget)
            elif isinstance(widget, QtGui.QDockWidget):
                dwArea = self.wprops.getAttribute(elem, "dockWidgetArea")
                topwidget.addDockWidget(QtCore.Qt.DockWidgetArea(dwArea),
                        widget)

    def handleHeaderView(self, elem, name, header):
        value = self.wprops.getAttribute(elem, name + "Visible")
        if value is not None:
            header.setVisible(value)

        value = self.wprops.getAttribute(elem, name + "CascadingSectionResizes")
        if value is not None:
            header.setCascadingSectionResizes(value)

        value = self.wprops.getAttribute(elem, name + "DefaultSectionSize")
        if value is not None:
            header.setDefaultSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "HighlightSections")
        if value is not None:
            header.setHighlightSections(value)

        value = self.wprops.getAttribute(elem, name + "MinimumSectionSize")
        if value is not None:
            header.setMinimumSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "ShowSortIndicator")
        if value is not None:
            header.setSortIndicatorShown(value)

        value = self.wprops.getAttribute(elem, name + "StretchLastSection")
        if value is not None:
            header.setStretchLastSection(value)

    def createSpacer(self, elem):
        width = elem.findtext("property/size/width")
        height = elem.findtext("property/size/height")

        if width is None or height is None:
            size_args = ()
        else:
            size_args = (int(width), int(height))

        sizeType = self.wprops.getProperty(elem, "sizeType",
                QtGui.QSizePolicy.Expanding)

        policy = (QtGui.QSizePolicy.Minimum, sizeType)

        if self.wprops.getProperty(elem, "orientation") == QtCore.Qt.Horizontal:
            policy = policy[1], policy[0]

        spacer = self.factory.createQObject("QSpacerItem",
                self.uniqueName("spacerItem"), size_args + policy,
                is_attribute=False)

        if self.stack.topIsLayout():
            lay = self.stack.peek()
            lp = elem.attrib['layout-position']

            if isinstance(lay, QtGui.QFormLayout):
                lay.setItem(lp[0], self._form_layout_role(lp), spacer)
            else:
                lay.addItem(spacer, *lp)

    def createLayout(self, elem):
        # We use an internal property to handle margins which will use separate
        # left, top, right and bottom margins if they are found to be
        # different.  The following will select, in order of preference,
        # separate margins, the same margin in all directions, and the default
        # margin.
        margin = self.wprops.getProperty(elem, 'margin',
                self.defaults['margin'])
        left = self.wprops.getProperty(elem, 'leftMargin', margin)
        top = self.wprops.getProperty(elem, 'topMargin', margin)
        right = self.wprops.getProperty(elem, 'rightMargin', margin)
        bottom = self.wprops.getProperty(elem, 'bottomMargin', margin)

        if left >= 0 or top >= 0 or right >= 0 or bottom >= 0:
            # We inject the new internal property.
            cme = SubElement(elem, 'property', name='pyuicMargins')
            SubElement(cme, 'number').text = str(left)
            SubElement(cme, 'number').text = str(top)
            SubElement(cme, 'number').text = str(right)
            SubElement(cme, 'number').text = str(bottom)

        # We use an internal property to handle spacing which will use separate
        # horizontal and vertical spacing if they are found to be different.
        # The following will select, in order of preference, separate
        # horizontal and vertical spacing, the same spacing in both directions,
        # and the default spacing.
        spacing = self.wprops.getProperty(elem, 'spacing',
                self.defaults['spacing'])
        horiz = self.wprops.getProperty(elem, 'horizontalSpacing', spacing)
        vert = self.wprops.getProperty(elem, 'verticalSpacing', spacing)

        if horiz >= 0 or vert >= 0:
            # We inject the new internal property.
            cme = SubElement(elem, 'property', name='pyuicSpacing')
            SubElement(cme, 'number').text = str(horiz)
            SubElement(cme, 'number').text = str(vert)

        classname = elem.attrib["class"]
        if self.stack.topIsLayout():
            parent = None
        else:
            parent = self.stack.topwidget
        if "name" not in elem.attrib:
            elem.attrib["name"] = classname[1:].lower()
        self.stack.push(self.setupObject(classname, parent, elem))
        self.traverseWidgetTree(elem)

        layout = self.stack.popLayout()
        self.configureLayout(elem, layout)

        if self.stack.topIsLayout():
            top_layout = self.stack.peek()
            lp = elem.attrib['layout-position']

            if isinstance(top_layout, QtGui.QFormLayout):
                top_layout.setLayout(lp[0], self._form_layout_role(lp), layout)
            else:
                top_layout.addLayout(layout, *lp)

    def configureLayout(self, elem, layout):
        if isinstance(layout, QtGui.QGridLayout):
            self.setArray(elem, 'columnminimumwidth',
                    layout.setColumnMinimumWidth)
            self.setArray(elem, 'rowminimumheight',
                    layout.setRowMinimumHeight)
            self.setArray(elem, 'columnstretch', layout.setColumnStretch)
            self.setArray(elem, 'rowstretch', layout.setRowStretch)

        elif isinstance(layout, QtGui.QBoxLayout):
            self.setArray(elem, 'stretch', layout.setStretch)

    def setArray(self, elem, name, setter):
        array = elem.attrib.get(name)
        if array:
            for idx, value in enumerate(array.split(',')):
                value = int(value)
                if value > 0:
                    setter(idx, value)

    def disableSorting(self, w):
        if self.item_nr == 0:
            self.sorting_enabled = self.factory.invoke("__sortingEnabled",
                    w.isSortingEnabled)
            w.setSortingEnabled(False)

    def handleItem(self, elem):
        if self.stack.topIsLayout():
            elem[0].attrib['layout-position'] = _layout_position(elem)
            self.traverseWidgetTree(elem)
        else:
            w = self.stack.topwidget

            if isinstance(w, QtGui.QComboBox):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")

                if icon:
                    w.addItem(icon, '')
                else:
                    w.addItem('')

                w.setItemText(self.item_nr, text)

            elif isinstance(w, QtGui.QListWidget):
                self.disableSorting(w)
                item = self.createWidgetItem('QListWidgetItem', elem, w.item,
                        self.item_nr)
                w.addItem(item)

            elif isinstance(w, QtGui.QTreeWidget):
                if self.itemstack:
                    parent, _ = self.itemstack[-1]
                    _, nr_in_root = self.itemstack[0]
                else:
                    parent = w
                    nr_in_root = self.item_nr

                item = self.factory.createQObject("QTreeWidgetItem",
                        "item_%d" % len(self.itemstack), (parent, ), False)

                if self.item_nr == 0 and not self.itemstack:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                self.itemstack.append((item, self.item_nr))
                self.item_nr = 0

                # We have to access the item via the tree when setting the
                # text.
                titm = w.topLevelItem(nr_in_root)
                for child, nr_in_parent in self.itemstack[1:]:
                    titm = titm.child(nr_in_parent)

                column = -1
                for prop in elem.findall('property'):
                    c_prop = self.wprops.convert(prop)
                    c_prop_name = prop.attrib['name']

                    if c_prop_name == 'text':
                        column += 1
                        if c_prop:
                            titm.setText(column, c_prop)
                    elif c_prop_name == 'statusTip':
                        item.setStatusTip(column, c_prop)
                    elif c_prop_name == 'toolTip':
                        item.setToolTip(column, c_prop)
                    elif c_prop_name == 'whatsThis':
                        item.setWhatsThis(column, c_prop)
                    elif c_prop_name == 'font':
                        item.setFont(column, c_prop)
                    elif c_prop_name == 'icon':
                        item.setIcon(column, c_prop)
                    elif c_prop_name == 'background':
                        item.setBackground(column, c_prop)
                    elif c_prop_name == 'foreground':
                        item.setForeground(column, c_prop)
                    elif c_prop_name == 'flags':
                        item.setFlags(c_prop)
                    elif c_prop_name == 'checkState':
                        item.setCheckState(column, c_prop)

                self.traverseWidgetTree(elem)
                _, self.item_nr = self.itemstack.pop()

            elif isinstance(w, QtGui.QTableWidget):
                row = int(elem.attrib['row'])
                col = int(elem.attrib['column'])

                self.disableSorting(w)
                item = self.createWidgetItem('QTableWidgetItem', elem, w.item,
                        row, col)
                w.setItem(row, col, item)

            self.item_nr += 1

    def addAction(self, elem):
        self.actions.append((self.stack.topwidget, elem.attrib["name"]))

    @staticmethod
    def any_i18n(*args):
        """ Return True if any argument appears to be an i18n string. """

        for a in args:
            if a is not None and not isinstance(a, str):
                return True

        return False

    def createWidgetItem(self, item_type, elem, getter, *getter_args):
        """ Create a specific type of widget item. """

        item = self.factory.createQObject(item_type, "item", (), False)
        props = self.wprops

        # Note that not all types of widget items support the full set of
        # properties.

        text = props.getProperty(elem, 'text')
        status_tip = props.getProperty(elem, 'statusTip')
        tool_tip = props.getProperty(elem, 'toolTip')
        whats_this = props.getProperty(elem, 'whatsThis')

        if self.any_i18n(text, status_tip, tool_tip, whats_this):
            self.factory.invoke("item", getter, getter_args)

        if text:
            item.setText(text)

        if status_tip:
            item.setStatusTip(status_tip)

        if tool_tip:
            item.setToolTip(tool_tip)

        if whats_this:
            item.setWhatsThis(whats_this)

        text_alignment = props.getProperty(elem, 'textAlignment')
        if text_alignment:
            item.setTextAlignment(text_alignment)

        font = props.getProperty(elem, 'font')
        if font:
            item.setFont(font)

        icon = props.getProperty(elem, 'icon')
        if icon:
            item.setIcon(icon)

        background = props.getProperty(elem, 'background')
        if background:
            item.setBackground(background)

        foreground = props.getProperty(elem, 'foreground')
        if foreground:
            item.setForeground(foreground)

        flags = props.getProperty(elem, 'flags')
        if flags:
            item.setFlags(flags)

        check_state = props.getProperty(elem, 'checkState')
        if check_state:
            item.setCheckState(check_state)

        return item

    def addHeader(self, elem):
        w = self.stack.topwidget

        if isinstance(w, QtGui.QTreeWidget):
            props = self.wprops
            col = self.column_counter

            text = props.getProperty(elem, 'text')
            if text:
                w.headerItem().setText(col, text)

            status_tip = props.getProperty(elem, 'statusTip')
            if status_tip:
                w.headerItem().setStatusTip(col, status_tip)

            tool_tip = props.getProperty(elem, 'toolTip')
            if tool_tip:
                w.headerItem().setToolTip(col, tool_tip)

            whats_this = props.getProperty(elem, 'whatsThis')
            if whats_this:
                w.headerItem().setWhatsThis(col, whats_this)

            text_alignment = props.getProperty(elem, 'textAlignment')
            if text_alignment:
                w.headerItem().setTextAlignment(col, text_alignment)

            font = props.getProperty(elem, 'font')
            if font:
                w.headerItem().setFont(col, font)

            icon = props.getProperty(elem, 'icon')
            if icon:
                w.headerItem().setIcon(col, icon)

            background = props.getProperty(elem, 'background')
            if background:
                w.headerItem().setBackground(col, background)

            foreground = props.getProperty(elem, 'foreground')
            if foreground:
                w.headerItem().setForeground(col, foreground)

            self.column_counter += 1

        elif isinstance(w, QtGui.QTableWidget):
            if len(elem) != 0:
                if elem.tag == 'column':
                    item = self.createWidgetItem('QTableWidgetItem', elem,
                            w.horizontalHeaderItem, self.column_counter)
                    w.setHorizontalHeaderItem(self.column_counter, item)
                    self.column_counter += 1
                elif elem.tag == 'row':
                    item = self.createWidgetItem('QTableWidgetItem', elem,
                            w.verticalHeaderItem, self.row_counter)
                    w.setVerticalHeaderItem(self.row_counter, item)
                    self.row_counter += 1

    def setZOrder(self, elem):
        # Designer can generate empty zorder elements.
        if elem.text is None:
            return

        # Designer allows the z-order of spacer items to be specified even
        # though they can't be raised, so ignore any missing raise_() method.
        try:
            getattr(self.toplevelWidget, elem.text).raise_()
        except AttributeError:
            # Note that uic issues a warning message.
            pass

    def createAction(self, elem):
        self.setupObject("QAction", self.currentActionGroup or self.toplevelWidget,
                         elem)

    def createActionGroup(self, elem):
        action_group = self.setupObject("QActionGroup", self.toplevelWidget, elem)
        self.currentActionGroup = action_group
        self.traverseWidgetTree(elem)
        self.currentActionGroup = None

    widgetTreeItemHandlers = {
        "widget"    : createWidget,
        "addaction" : addAction,
        "layout"    : createLayout,
        "spacer"    : createSpacer,
        "item"      : handleItem,
        "action"    : createAction,
        "actiongroup": createActionGroup,
        "column"    : addHeader,
        "row"       : addHeader,
        "zorder"    : setZOrder,
        }

    def traverseWidgetTree(self, elem):
        for child in iter(elem):
            try:
                handler = self.widgetTreeItemHandlers[child.tag]
            except KeyError: 
                continue

            handler(self, child)

    def createUserInterface(self, elem):
        # Get the names of the class and widget.
        cname = elem.attrib["class"]
        wname = elem.attrib["name"]

        # If there was no widget name then derive it from the class name.
        if not wname:
            wname = cname

            if wname.startswith("Q"):
                wname = wname[1:]

            wname = wname[0].lower() + wname[1:]

        self.toplevelWidget = self.createToplevelWidget(cname, wname)
        self.toplevelWidget.setObjectName(wname)
        DEBUG("toplevel widget is %s",
              self.toplevelWidget.metaObject().className())
        self.wprops.setProperties(self.toplevelWidget, elem)
        self.stack.push(self.toplevelWidget)
        self.traverseWidgetTree(elem)
        self.stack.popWidget()
        self.addActions()
        self.setBuddies()
        self.setDelayedProps()
        
    def addActions(self):
        for widget, action_name in self.actions:
            if action_name == "separator":
                widget.addSeparator()
            else:
                DEBUG("add action %s to %s", action_name, widget.objectName())
                action_obj = getattr(self.toplevelWidget, action_name)
                if isinstance(action_obj, QtGui.QMenu):
                    widget.addAction(action_obj.menuAction())
                elif not isinstance(action_obj, QtGui.QActionGroup):
                    widget.addAction(action_obj)

    def setDelayedProps(self):
        for widget, layout, setter, args in self.wprops.delayed_props:
            if layout:
                widget = widget.layout()

            setter = getattr(widget, setter)
            setter(args)
            
    def setBuddies(self):
        for widget, buddy in self.wprops.buddies:
            DEBUG("%s is buddy of %s", buddy, widget.objectName())
            try:
                widget.setBuddy(getattr(self.toplevelWidget, buddy))
            except AttributeError:
                DEBUG("ERROR in ui spec: %s (buddy of %s) does not exist",
                      buddy, widget.objectName())

    def classname(self, elem):
        DEBUG("uiname is %s", elem.text)
        name = elem.text

        if name is None:
            name = ""

        self.uiname = name
        self.wprops.uiname = name
        self.setContext(name)

    def setContext(self, context):
        """
        Reimplemented by a sub-class if it needs to know the translation
        context.
        """
        pass

    def readDefaults(self, elem):
        self.defaults['margin'] = int(elem.attrib['margin'])
        self.defaults['spacing'] = int(elem.attrib['spacing'])

    def setTaborder(self, elem):
        lastwidget = None
        for widget_elem in elem:
            widget = getattr(self.toplevelWidget, widget_elem.text)

            if lastwidget is not None:
                self.toplevelWidget.setTabOrder(lastwidget, widget)

            lastwidget = widget

    def readResources(self, elem):
        """
        Read a "resources" tag and add the module to import to the parser's
        list of them.
        """
        try:
            iterator = getattr(elem, 'iter')
        except AttributeError:
            iterator = getattr(elem, 'getiterator')

        for include in iterator("include"):
            loc = include.attrib.get("location")

            # Apply the convention for naming the Python files generated by
            # pyrcc4.
            if loc and loc.endswith('.qrc'):
                mname = os.path.basename(loc[:-4] + self._resource_suffix)
                if mname not in self.resources:
                    self.resources.append(mname)

    def createConnections(self, elem):
        def name2object(obj):
            if obj == self.uiname:
                return self.toplevelWidget
            else:
                return getattr(self.toplevelWidget, obj)
        for conn in iter(elem):
            QtCore.QObject.connect(name2object(conn.findtext("sender")),
                                   QtCore.SIGNAL(conn.findtext("signal")),
                                   self.factory.getSlot(name2object(conn.findtext("receiver")),
                                                    conn.findtext("slot").split("(")[0]))
        QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget)

    def customWidgets(self, elem):
        def header2module(header):
            """header2module(header) -> string

            Convert paths to C++ header files to according Python modules
            >>> header2module("foo/bar/baz.h")
            'foo.bar.baz'
            """
            if header.endswith(".h"):
                header = header[:-2]

            mpath = []
            for part in header.split('/'):
                # Ignore any empty parts or those that refer to the current
                # directory.
                if part not in ('', '.'):
                    if part == '..':
                        # We should allow this for Python3.
                        raise SyntaxError("custom widget header file name may not contain '..'.")

                    mpath.append(part)

            return '.'.join(mpath)
    
        for custom_widget in iter(elem):
            classname = custom_widget.findtext("class")
            if classname.startswith("Q3"):
                raise NoSuchWidgetError(classname)
            self.factory.addCustomWidget(classname,
                                     custom_widget.findtext("extends") or "QWidget",
                                     header2module(custom_widget.findtext("header")))

    def createToplevelWidget(self, classname, widgetname):
        raise NotImplementedError

    def buttonGroups(self, elem):
        for button_group in iter(elem):
            if button_group.tag == 'buttongroup':
                bg_name = button_group.attrib['name']
                bg = ButtonGroup()
                self.button_groups[bg_name] = bg

                prop = self.getProperty(button_group, 'exclusive')
                if prop is not None:
                    if prop.findtext('bool') == 'false':
                        bg.exclusive = False
    
    # finalize will be called after the whole tree has been parsed and can be
    # overridden.
    def finalize(self):
        pass

    def parse(self, filename, resource_suffix, base_dir=''):
        self.wprops.set_base_dir(base_dir)

        self._resource_suffix = resource_suffix

        # The order in which the different branches are handled is important.
        # The widget tree handler relies on all custom widgets being known, and
        # in order to create the connections, all widgets have to be populated.
        branchHandlers = (
            ("layoutdefault", self.readDefaults),
            ("class",         self.classname),
            ("buttongroups",  self.buttonGroups),
            ("customwidgets", self.customWidgets),
            ("widget",        self.createUserInterface),
            ("connections",   self.createConnections),
            ("tabstops",      self.setTaborder),
            ("resources",     self.readResources),
        )

        document = parse(filename)
        version = document.getroot().attrib["version"]
        DEBUG("UI version is %s" % (version,))
        # Right now, only version 4.0 is supported.
        assert version in ("4.0",)
        for tagname, actor in branchHandlers:
            elem = document.find(tagname)
            if elem is not None:
                actor(elem)
        self.finalize()
        w = self.toplevelWidget
        self.reset()
        return w

    @staticmethod
    def _form_layout_role(layout_position):
        if layout_position[3] > 1:
            role = QtGui.QFormLayout.SpanningRole
        elif layout_position[1] == 1:
            role = QtGui.QFormLayout.FieldRole
        else:
            role = QtGui.QFormLayout.LabelRole

        return role
Пример #5
0
class UIParser(object):
    def __init__(self, QtCoreModule, QtGuiModule, creatorPolicy):
        self.factory = QObjectCreator(creatorPolicy)
        self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule)

        global QtCore, QtGui
        QtCore = QtCoreModule
        QtGui = QtGuiModule

        self.reset()

    def uniqueName(self, name):
        """UIParser.uniqueName(string) -> string

        Create a unique name from a string.
        >>> p = UIParser(QtCore, QtGui)
        >>> p.uniqueName("foo")
        'foo'
        >>> p.uniqueName("foo")
        'foo1'
        """
        try:
            suffix = self.name_suffixes[name]
        except KeyError:
            self.name_suffixes[name] = 0
            return name

        suffix += 1
        self.name_suffixes[name] = suffix

        return "%s%i" % (name, suffix)

    def reset(self):
        try: self.wprops.reset()
        except AttributeError: pass
        self.toplevelWidget = None
        self.stack = WidgetStack()
        self.name_suffixes = {}
        self.defaults = {"spacing": 6, "margin": 0}
        self.actions = []
        self.currentActionGroup = None
        self.resources = []
        self.button_groups = []
        self.layout_widget = False

    def setupObject(self, clsname, parent, branch, is_attribute = True):
        name = self.uniqueName(branch.attrib.get("name") or clsname[1:].lower())
        if parent is None:
            args = ()
        else:
            args = (parent, )
        obj =  self.factory.createQObject(clsname, name, args, is_attribute)
        self.wprops.setProperties(obj, branch)
        obj.setObjectName(name)
        if is_attribute:
            setattr(self.toplevelWidget, name, obj)
        return obj

    def createWidget(self, elem):
        self.column_counter = 0
        self.row_counter = 0
        self.item_nr = 0
        self.itemstack = []
        self.sorting_enabled = None

        widget_class = elem.attrib['class'].replace('::', '.')
        if widget_class == 'Line':
            widget_class = 'QFrame'

        # Ignore the parent if it is a container.
        parent = self.stack.topwidget
        if isinstance(parent, (QtGui.QDockWidget, QtGui.QMdiArea,
                               QtGui.QScrollArea, QtGui.QStackedWidget,
                               QtGui.QToolBox, QtGui.QTabWidget,
                               QtGui.QWizard)):
            parent = None

        # See if this is a layout widget.
        if widget_class == 'QWidget':
            if parent is not None:
                if not isinstance(parent, QtGui.QMainWindow):
                    self.layout_widget = True

        self.stack.push(self.setupObject(widget_class, parent, elem))

        if isinstance(self.stack.topwidget, QtGui.QTableWidget):
            self.stack.topwidget.setColumnCount(len(elem.findall("column")))
            self.stack.topwidget.setRowCount(len(elem.findall("row")))

        self.traverseWidgetTree(elem)
        widget = self.stack.popWidget()

        self.layout_widget = False

        if isinstance(widget, QtGui.QTreeView):
            self.handleHeaderView(elem, "header", widget.header())

        elif isinstance(widget, QtGui.QTableView):
            self.handleHeaderView(elem, "horizontalHeader",
                    widget.horizontalHeader())
            self.handleHeaderView(elem, "verticalHeader",
                    widget.verticalHeader())

        elif isinstance(widget, QtGui.QAbstractButton):
            bg_i18n = self.wprops.getAttribute(elem, "buttonGroup")
            if bg_i18n is not None:
                bg_name = bg_i18n.string

                for bg in self.button_groups:
                    if bg.objectName() == bg_name:
                        break
                else:
                    bg = self.factory.createQObject("QButtonGroup", bg_name,
                            (self.toplevelWidget, ))
                    bg.setObjectName(bg_name)
                    self.button_groups.append(bg)

                bg.addButton(widget)

        if self.sorting_enabled is not None:
            widget.setSortingEnabled(self.sorting_enabled)
            self.sorting_enabled = None

        if self.stack.topIsLayout():
            lay = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(lay, QtGui.QFormLayout):
                lay.setWidget(gp[0], self._form_layout_role(gp), widget)
            else:
                lay.addWidget(widget, *gp)

        topwidget = self.stack.topwidget

        if isinstance(topwidget, QtGui.QToolBox):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addItem(widget, icon, self.wprops.getAttribute(elem, "label"))
            else:
                topwidget.addItem(widget, self.wprops.getAttribute(elem, "label"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setItemToolTip(topwidget.indexOf(widget), tooltip)

        elif isinstance(topwidget, QtGui.QTabWidget):
            icon = self.wprops.getAttribute(elem, "icon")
            if icon is not None:
                topwidget.addTab(widget, icon, self.wprops.getAttribute(elem, "title"))
            else:
                topwidget.addTab(widget, self.wprops.getAttribute(elem, "title"))

            tooltip = self.wprops.getAttribute(elem, "toolTip")
            if tooltip is not None:
                topwidget.setTabToolTip(topwidget.indexOf(widget), tooltip)

        elif isinstance(topwidget, QtGui.QWizard):
            topwidget.addPage(widget)

        elif isinstance(topwidget, QtGui.QStackedWidget):
            topwidget.addWidget(widget)

        elif isinstance(topwidget, (QtGui.QDockWidget, QtGui.QScrollArea)):
            topwidget.setWidget(widget)

        elif isinstance(topwidget, QtGui.QMainWindow):
            if type(widget) == QtGui.QWidget:
                topwidget.setCentralWidget(widget)
            elif isinstance(widget, QtGui.QToolBar):
                tbArea = self.wprops.getAttribute(elem, "toolBarArea")

                if tbArea is None:
                    topwidget.addToolBar(widget)
                else:
                    topwidget.addToolBar(tbArea, widget)

                tbBreak = self.wprops.getAttribute(elem, "toolBarBreak")

                if tbBreak:
                    topwidget.insertToolBarBreak(widget)

            elif isinstance(widget, QtGui.QMenuBar):
                topwidget.setMenuBar(widget)
            elif isinstance(widget, QtGui.QStatusBar):
                topwidget.setStatusBar(widget)
            elif isinstance(widget, QtGui.QDockWidget):
                dwArea = self.wprops.getAttribute(elem, "dockWidgetArea")
                topwidget.addDockWidget(QtCore.Qt.DockWidgetArea(dwArea),
                        widget)

    def handleHeaderView(self, elem, name, header):
        value = self.wprops.getAttribute(elem, name + "Visible")
        if value is not None:
            header.setVisible(value)

        value = self.wprops.getAttribute(elem, name + "CascadingSectionResizes")
        if value is not None:
            header.setCascadingSectionResizes(value)

        value = self.wprops.getAttribute(elem, name + "DefaultSectionSize")
        if value is not None:
            header.setDefaultSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "HighlightSections")
        if value is not None:
            header.setHighlightSections(value)

        value = self.wprops.getAttribute(elem, name + "MinimumSectionSize")
        if value is not None:
            header.setMinimumSectionSize(value)

        value = self.wprops.getAttribute(elem, name + "ShowSortIndicator")
        if value is not None:
            header.setSortIndicatorShown(value)

        value = self.wprops.getAttribute(elem, name + "StretchLastSection")
        if value is not None:
            header.setStretchLastSection(value)

    def createSpacer(self, elem):
        width = elem.findtext("property/size/width")
        height = elem.findtext("property/size/height")

        if width is None or height is None:
            size_args = ()
        else:
            size_args = (int(width), int(height))

        sizeType = self.wprops.getProperty(elem, "sizeType",
                QtGui.QSizePolicy.Expanding)

        policy = (QtGui.QSizePolicy.Minimum, sizeType)

        if self.wprops.getProperty(elem, "orientation") == QtCore.Qt.Horizontal:
            policy = policy[1], policy[0]

        spacer = self.factory.createQObject("QSpacerItem",
                self.uniqueName("spacerItem"), size_args + policy,
                is_attribute=False)

        if self.stack.topIsLayout():
            lay = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(lay, QtGui.QFormLayout):
                lay.setItem(gp[0], self._form_layout_role(gp), spacer)
            else:
                lay.addItem(spacer, *gp)

    def createLayout(self, elem):
        # Qt v4.3 introduced setContentsMargins() and separate values for each
        # of the four margins which are specified as separate properties.  This
        # doesn't really fit the way we parse the tree (why aren't the values
        # passed as attributes of a single property?) so we create a new
        # property and inject it.  However, if we find that they have all been
        # specified and have the same value then we inject a different property
        # that is compatible with older versions of Qt.
        left = self.wprops.getProperty(elem, 'leftMargin', -1)
        top = self.wprops.getProperty(elem, 'topMargin', -1)
        right = self.wprops.getProperty(elem, 'rightMargin', -1)
        bottom = self.wprops.getProperty(elem, 'bottomMargin', -1)

        # Count the number of properties and if they had the same value.
        def comp_property(m, so_far=-2, nr=0):
            if m >= 0:
                nr += 1

                if so_far == -2:
                    so_far = m
                elif so_far != m:
                    so_far = -1

            return so_far, nr

        margin, nr_margins = comp_property(left)
        margin, nr_margins = comp_property(top, margin, nr_margins)
        margin, nr_margins = comp_property(right, margin, nr_margins)
        margin, nr_margins = comp_property(bottom, margin, nr_margins)

        if nr_margins > 0:
            if nr_margins == 4 and margin >= 0:
                # We can inject the old margin property.
                me = SubElement(elem, 'property', name='margin')
                SubElement(me, 'number').text = str(margin)
            else:
                # We have to inject the new internal property.
                cme = SubElement(elem, 'property', name='pyuicContentsMargins')
                SubElement(cme, 'number').text = str(left)
                SubElement(cme, 'number').text = str(top)
                SubElement(cme, 'number').text = str(right)
                SubElement(cme, 'number').text = str(bottom)
        elif self.layout_widget:
            # The layout's of layout widgets have no margin.
            me = SubElement(elem, 'property', name='margin')
            SubElement(me, 'number').text = '0'

            # In case there are any nested layouts.
            self.layout_widget = False

        # We do the same for setHorizontalSpacing() and setVerticalSpacing().
        horiz = self.wprops.getProperty(elem, 'horizontalSpacing', -1)
        vert = self.wprops.getProperty(elem, 'verticalSpacing', -1)

        if horiz >= 0 or vert >= 0:
            # We inject the new internal property.
            cme = SubElement(elem, 'property', name='pyuicSpacing')
            SubElement(cme, 'number').text = str(horiz)
            SubElement(cme, 'number').text = str(vert)

        classname = elem.attrib["class"]
        if self.stack.topIsLayout():
            parent = None
        else:
            parent = self.stack.topwidget
        if "name" not in elem.attrib:
            elem.attrib["name"] = classname[1:].lower()
        self.stack.push(self.setupObject(classname, parent, elem))
        self.traverseWidgetTree(elem)

        layout = self.stack.popLayout()
        self.configureLayout(elem, layout)

        if self.stack.topIsLayout():
            top_layout = self.stack.peek()
            gp = elem.attrib["grid-position"]

            if isinstance(top_layout, QtGui.QFormLayout):
                top_layout.setLayout(gp[0], self._form_layout_role(gp), layout)
            else:
                top_layout.addLayout(layout, *gp)

    def configureLayout(self, elem, layout):
        if isinstance(layout, QtGui.QGridLayout):
            self.setArray(elem, 'columnminimumwidth',
                    layout.setColumnMinimumWidth)
            self.setArray(elem, 'rowminimumheight',
                    layout.setRowMinimumHeight)
            self.setArray(elem, 'columnstretch', layout.setColumnStretch)
            self.setArray(elem, 'rowstretch', layout.setRowStretch)

        elif isinstance(layout, QtGui.QBoxLayout):
            self.setArray(elem, 'stretch', layout.setStretch)

    def setArray(self, elem, name, setter):
        array = elem.attrib.get(name)
        if array:
            for idx, value in enumerate(array.split(',')):
                value = int(value)
                if value > 0:
                    setter(idx, value)

    def handleItem(self, elem):
        if self.stack.topIsLayout():
            elem[0].attrib["grid-position"] = gridPosition(elem)
            self.traverseWidgetTree(elem)
        else:
            w = self.stack.topwidget

            if isinstance(w, QtGui.QComboBox):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")

                if icon:
                    w.addItem(icon, '')
                else:
                    w.addItem('')

                w.setItemText(self.item_nr, text)

            elif isinstance(w, QtGui.QListWidget):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")
                flags = self.wprops.getProperty(elem, "flags")
                check_state = self.wprops.getProperty(elem, "checkState")
                background = self.wprops.getProperty(elem, "background")
                foreground = self.wprops.getProperty(elem, "foreground")

                if icon or flags or check_state:
                    item_name = "item"
                else:
                    item_name = None

                item = self.factory.createQObject("QListWidgetItem", item_name,
                        (w, ), False)

                if self.item_nr == 0:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                if text:
                    w.item(self.item_nr).setText(text)

                if icon:
                    item.setIcon(icon)

                if flags:
                    item.setFlags(flags)

                if check_state:
                    item.setCheckState(check_state)

                if background:
                    item.setBackground(background)

                if foreground:
                    item.setForeground(foreground)

            elif isinstance(w, QtGui.QTreeWidget):
                if self.itemstack:
                    parent, _ = self.itemstack[-1]
                    _, nr_in_root = self.itemstack[0]
                else:
                    parent = w
                    nr_in_root = self.item_nr

                item = self.factory.createQObject("QTreeWidgetItem",
                        "item_%d" % len(self.itemstack), (parent, ), False)

                if self.item_nr == 0 and not self.itemstack:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                self.itemstack.append((item, self.item_nr))
                self.item_nr = 0

                # We have to access the item via the tree when setting the
                # text.
                titm = w.topLevelItem(nr_in_root)
                for child, nr_in_parent in self.itemstack[1:]:
                    titm = titm.child(nr_in_parent)

                column = -1
                for prop in elem.findall("property"):
                    c_prop = self.wprops.convert(prop)
                    c_prop_name = prop.attrib["name"]

                    if c_prop_name == "text":
                        column += 1
                        if c_prop:
                            titm.setText(column, c_prop)
                    elif c_prop_name == "icon":
                        item.setIcon(column, c_prop)
                    elif c_prop_name == "flags":
                        item.setFlags(c_prop)
                    elif c_prop_name == "checkState":
                        item.setCheckState(column, c_prop)
                    elif c_prop_name == "background":
                        item.setBackground(column, c_prop)
                    elif c_prop_name == "foreground":
                        item.setForeground(column, c_prop)

                self.traverseWidgetTree(elem)
                _, self.item_nr = self.itemstack.pop()

            elif isinstance(w, QtGui.QTableWidget):
                text = self.wprops.getProperty(elem, "text")
                icon = self.wprops.getProperty(elem, "icon")
                flags = self.wprops.getProperty(elem, "flags")
                check_state = self.wprops.getProperty(elem, "checkState")
                background = self.wprops.getProperty(elem, "background")
                foreground = self.wprops.getProperty(elem, "foreground")

                item = self.factory.createQObject("QTableWidgetItem", "item",
                        (), False)

                if self.item_nr == 0:
                    self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled)
                    w.setSortingEnabled(False)

                row = int(elem.attrib["row"])
                col = int(elem.attrib["column"])

                if icon:
                    item.setIcon(icon)

                if flags:
                    item.setFlags(flags)

                if check_state:
                    item.setCheckState(check_state)

                if background:
                    item.setBackground(background)

                if foreground:
                    item.setForeground(foreground)

                w.setItem(row, col, item)

                if text:
                    # Text is translated so we don't have access to the item
                    # attribute when generating code so we must get it from the
                    # widget after it has been set.
                    w.item(row, col).setText(text)

            self.item_nr += 1

    def addAction(self, elem):
        self.actions.append((self.stack.topwidget, elem.attrib["name"]))

    def addHeader(self, elem):
        w = self.stack.topwidget

        if isinstance(w, QtGui.QTreeWidget):
            text = self.wprops.getProperty(elem, "text")
            icon = self.wprops.getProperty(elem, "icon")

            if text:
                w.headerItem().setText(self.column_counter, text)

            if icon:
                w.headerItem().setIcon(self.column_counter, icon)

            self.column_counter += 1

        elif isinstance(w, QtGui.QTableWidget):
            if len(elem) == 0:
                return

            text = self.wprops.getProperty(elem, "text")
            icon = self.wprops.getProperty(elem, "icon")

            item = self.factory.createQObject("QTableWidgetItem", "item",
                        (), False)

            if elem.tag == "column":
                w.setHorizontalHeaderItem(self.column_counter, item)

                if text:
                    w.horizontalHeaderItem(self.column_counter).setText(text)

                if icon:
                    item.setIcon(icon)

                self.column_counter += 1
            elif elem.tag == "row":
                w.setVerticalHeaderItem(self.row_counter, item)

                if text:
                    w.verticalHeaderItem(self.row_counter).setText(text)

                if icon:
                    item.setIcon(icon)

                self.row_counter += 1

    def createAction(self, elem):
        self.setupObject("QAction", self.currentActionGroup or self.toplevelWidget,
                         elem)

    def createActionGroup(self, elem):
        action_group = self.setupObject("QActionGroup", self.toplevelWidget, elem)
        self.currentActionGroup = action_group
        self.traverseWidgetTree(elem)
        self.currentActionGroup = None

    widgetTreeItemHandlers = {
        "widget"    : createWidget,
        "addaction" : addAction,
        "layout"    : createLayout,
        "spacer"    : createSpacer,
        "item"      : handleItem,
        "action"    : createAction,
        "actiongroup": createActionGroup,
        "column"    : addHeader,
        "row"       : addHeader,
        }

    def traverseWidgetTree(self, elem):
        for child in iter(elem):
            try:
                handler = self.widgetTreeItemHandlers[child.tag]
            except KeyError:
                continue

            handler(self, child)

    def createUserInterface(self, elem):
        # Get the names of the class and widget.
        cname = elem.attrib["class"]
        wname = elem.attrib["name"]

        # If there was no widget name then derive it from the class name.
        if not wname:
            wname = cname

            if wname.startswith("Q"):
                wname = wname[1:]

            wname = wname[0].lower() + wname[1:]

        self.toplevelWidget = self.createToplevelWidget(cname, wname)
        self.toplevelWidget.setObjectName(wname)
        DEBUG("toplevel widget is %s",
              self.toplevelWidget.metaObject().className())
        self.wprops.setProperties(self.toplevelWidget, elem)
        self.stack.push(self.toplevelWidget)
        self.traverseWidgetTree(elem)
        self.stack.popWidget()
        self.addActions()
        self.setBuddies()
        self.setDelayedProps()

    def addActions(self):
        for widget, action_name in self.actions:
            if action_name == "separator":
                widget.addSeparator()
            else:
                DEBUG("add action %s to %s", action_name, widget.objectName())
                action_obj = getattr(self.toplevelWidget, action_name)
                if isinstance(action_obj, QtGui.QMenu):
                    widget.addAction(action_obj.menuAction())
                elif not isinstance(action_obj, QtGui.QActionGroup):
                    widget.addAction(action_obj)

    def setDelayedProps(self):
        for widget, layout, setter, args in self.wprops.delayed_props:
            if layout:
                widget = widget.layout()

            setter = getattr(widget, setter)
            setter(args)

    def setBuddies(self):
        for widget, buddy in self.wprops.buddies:
            DEBUG("%s is buddy of %s", buddy, widget.objectName())
            try:
                widget.setBuddy(getattr(self.toplevelWidget, buddy))
            except AttributeError:
                DEBUG("ERROR in ui spec: %s (buddy of %s) does not exist",
                      buddy, widget.objectName())

    def classname(self, elem):
        DEBUG("uiname is %s", elem.text)
        name = elem.text

        if name is None:
            name = ""

        self.uiname = name
        self.wprops.uiname = name
        self.setContext(name)

    def setContext(self, context):
        """
        Reimplemented by a sub-class if it needs to know the translation
        context.
        """
        pass

    def readDefaults(self, elem):
        self.defaults["margin"] = int(elem.attrib["margin"])
        self.defaults["spacing"] = int(elem.attrib["spacing"])

    def setTaborder(self, elem):
        lastwidget = None
        for widget_elem in elem:
            widget = getattr(self.toplevelWidget, widget_elem.text)

            if lastwidget is not None:
                self.toplevelWidget.setTabOrder(lastwidget, widget)

            lastwidget = widget

    def readResources(self, elem):
        """
        Read a "resources" tag and add the module to import to the parser's
        list of them.
        """
        for include in elem.getiterator("include"):
            loc = include.attrib.get("location")

            # Assume our convention for naming the Python files generated by
            # pyrcc4.
            if loc and loc.endswith('.qrc'):
                self.resources.append(os.path.basename(loc[:-4] + '_rc'))

    def createConnections(self, elem):
        def name2object(obj):
            if obj == self.uiname:
                return self.toplevelWidget
            else:
                return getattr(self.toplevelWidget, obj)
        for conn in iter(elem):
            QtCore.QObject.connect(name2object(conn.findtext("sender")),
                                   QtCore.SIGNAL(conn.findtext("signal")),
                                   self.factory.getSlot(name2object(conn.findtext("receiver")),
                                                    conn.findtext("slot").split("(")[0]))
        QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget)

    def customWidgets(self, elem):
        def header2module(header):
            """header2module(header) -> string

            Convert paths to C++ header files to according Python modules
            >>> header2module("foo/bar/baz.h")
            'foo.bar.baz'
            """
            if header.endswith(".h"):
                header = header[:-2]

            mpath = []
            for part in header.split('/'):
                # Ignore any empty parts or those that refer to the current
                # directory.
                if part not in ('', '.'):
                    if part == '..':
                        # We should allow this for Python3.
                        raise SyntaxError("custom widget header file name may not contain '..'.")

                    mpath.append(part)

            return '.'.join(mpath)

        for custom_widget in iter(elem):
            classname = custom_widget.findtext("class")
            if classname.startswith("Q3"):
                raise NoSuchWidgetError(classname)
            self.factory.addCustomWidget(classname,
                                     custom_widget.findtext("extends") or "QWidget",
                                     header2module(custom_widget.findtext("header")))

    def createToplevelWidget(self, classname, widgetname):
        raise NotImplementedError

    # finalize will be called after the whole tree has been parsed and can be
    # overridden.
    def finalize(self):
        pass

    def parse(self, filename, base_dir=''):
        self.wprops.set_base_dir(base_dir)

        # The order in which the different branches are handled is important.
        # The widget tree handler relies on all custom widgets being known, and
        # in order to create the connections, all widgets have to be populated.
        branchHandlers = (
            ("layoutdefault", self.readDefaults),
            ("class",         self.classname),
            ("customwidgets", self.customWidgets),
            ("widget",        self.createUserInterface),
            ("connections",   self.createConnections),
            ("tabstops",      self.setTaborder),
            ("resources",     self.readResources),
        )

        document = parse(filename)
        version = document.getroot().attrib["version"]
        DEBUG("UI version is %s" % (version,))
        # Right now, only version 4.0 is supported.
        assert version in ("4.0",)
        for tagname, actor in branchHandlers:
            elem = document.find(tagname)
            if elem is not None:
                actor(elem)
        self.finalize()
        w = self.toplevelWidget
        self.reset()
        return w

    @staticmethod
    def _form_layout_role(grid_position):
        if grid_position[3] > 1:
            role = QtGui.QFormLayout.SpanningRole
        elif grid_position[1] == 1:
            role = QtGui.QFormLayout.FieldRole
        else:
            role = QtGui.QFormLayout.LabelRole

        return role