diff frontends/src/tools/xmlui.py @ 802:9007bb133009

core, frontends: XMLUI refactoring: - XMLUI now use objects with 2 main classes: widgets (button, label, etc), and container which contain widgets according to a layout - widgets and containers classes are found through introspection, thereby it's really easy to add a new one - there is still the AddWidgetName helper, for example AddText('jid', 'test@example.net') will add a StringWidget with name "jid" and default value "test@example.net" - container can be inside other containers. changeContainer change the first parent container
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:19:00 +0100
parents 46aa5ada61bf
children f100fd8d279f
line wrap: on
line diff
--- a/frontends/src/tools/xmlui.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/frontends/src/tools/xmlui.py	Tue Feb 04 18:19:00 2014 +0100
@@ -89,9 +89,6 @@
     """ A widget able to show/choose one or several strings in a list """
 
 
-class AdvancedListWidget(Widget):
-    pass #TODO
-
 class Container(Widget):
     """ Widget which can contain other ones with a specific layout """
 
@@ -157,103 +154,60 @@
             flags = []
         self.flags = flags
         self.ctrl_list = {}  # usefull to access ctrl
+        self._main_cont = None
         self.constructUI(xml_data)
 
-    def _parseElems(self, node, parent, post_treat=None):
-        """ Parse elements inside a <layout> tags, and add them to the parent
-        @param node: current XMLUI node
-        @param parent: parent container
-        @param post_treat: frontend specific treatments do to on each element
+    @property
+    def main_cont(self):
+        return self._main_cont
 
-        """
-        for elem in node.childNodes:
-            if elem.nodeName != "elem":
-                raise NotImplementedError(_('Unknown tag [%s]') % elem.nodeName)
-            id_ = elem.getAttribute("id")
-            name = elem.getAttribute("name")
-            type_ = elem.getAttribute("type")
-            value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
-            if type_=="empty":
-                ctrl = self.widget_factory.createEmptyWidget(parent)
-            elif type_=="text":
-                try:
-                    value = elem.childNodes[0].wholeText
-                except IndexError:
-                    warning (_("text node has no child !"))
-                ctrl = self.widget_factory.createTextWidget(parent, value)
-            elif type_=="label":
-                ctrl = self.widget_factory.createTextWidget(parent, value+": ")
-            elif type_=="string":
-                ctrl = self.widget_factory.createStringWidget(parent, value)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="password":
-                ctrl = self.widget_factory.createPasswordWidget(parent, value)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="textbox":
-                ctrl = self.widget_factory.createTextBoxWidget(parent, value)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="bool":
-                ctrl = self.widget_factory.createBoolWidget(parent, value=='true')
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="list":
-                style=[] if elem.getAttribute("multi")=='yes' else ['single']
-                _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")]
-                ctrl = self.widget_factory.createListWidget(parent, _options, style)
-                ctrl._xmluiSelectValue(elem.getAttribute("value"))
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="button":
-                callback_id = elem.getAttribute("callback")
-                ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress)
-                ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
-            elif type_=="advanced_list":
-                _options = [getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")]
-                ctrl = self.widget_factory.createListWidget(parent, _options, ['can_select_none'])
-                ctrl._xmluiSelectValue(elem.getAttribute("value"))
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            else:
-                error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
-                raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
+    @main_cont.setter
+    def main_cont(self, value):
+        if self._main_cont is not None:
+            raise ValueError(_("XMLUI can have only one main container"))
+        self._main_cont = value
 
-            if self.type == 'param':
-                try:
-                    ctrl._xmluiOnChange(self.onParamChange)
-                    ctrl._param_category = self._current_category
-                    ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1]
-                except AttributeError:
-                    if not isinstance(ctrl, (EmptyWidget, TextWidget)):
-                        warning(_("No change listener on [%s]" % ctrl))
 
-            if post_treat is not None:
-                post_treat(ctrl, id_, name, type_, value)
-            parent._xmluiAppend(ctrl)
-
-    def _parseChilds(self, current, elem, wanted = ['layout'], data = None):
+    def _parseChilds(self, parent, current_node, wanted = ('container',), data = None):
         """ Recursively parse childNodes of an elemen
-        @param current: widget container with '_xmluiAppend' method
-        @param elem: element from which childs will be parsed
+        @param parent: widget container with '_xmluiAppend' method
+        @param current_node: element from which childs will be parsed
         @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant
         @param data: additionnal data which are needed in some cases
 
         """
-        for node in elem.childNodes:
+        for node in current_node.childNodes:
             if wanted and not node.nodeName in wanted:
                 raise InvalidXMLUI
-            if node.nodeName == "layout":
+
+            if node.nodeName == "container":
                 type_ = node.getAttribute('type')
+                if parent is self and type_ != 'vertical':
+                    # main container is not a VerticalContainer and we want one, so we create one to wrap it
+                    parent = self.widget_factory.createVerticalContainer(self)
+                    self.main_cont = parent
                 if type_ == "tabs":
-                    tab_cont = self.widget_factory.createTabsContainer(current)
-                    self._parseChilds(current, node, ['category'], tab_cont)
-                    current._xmluiAppend(tab_cont)
+                    cont = self.widget_factory.createTabsContainer(parent)
+                    self._parseChilds(parent, node, ('tab',), cont)
                 elif type_ == "vertical":
-                    self._parseElems(node, current)
+                    cont = self.widget_factory.createVerticalContainer(parent)
+                    self._parseChilds(cont, node, ('widget', 'container'))
                 elif type_ == "pairs":
-                    pairs = self.widget_factory.createPairsContainer(current)
-                    self._parseElems(node, pairs)
-                    current._xmluiAppend(pairs)
+                    cont = self.widget_factory.createPairsContainer(parent)
+                    self._parseChilds(cont, node, ('widget', 'container'))
                 else:
-                    warning(_("Unknown layout [%s], using default one") % type_)
-                    self._parseElems(node, current)
-            elif node.nodeName == "category":
+                    warning(_("Unknown container [%s], using default one") % type_)
+                    cont = self.widget_factory.createVerticalContainer(parent)
+                    self._parseChilds(cont, node, ('widget', 'container'))
+                try:
+                    parent._xmluiAppend(cont)
+                except AttributeError:
+                    if parent is self:
+                        self.main_cont = cont
+                    else:
+                        raise Exception(_("Internal Error, container has not _xmluiAppend method"))
+
+            elif node.nodeName == "tab":
                 name = node.getAttribute('name')
                 label = node.getAttribute('label')
                 if not name or not isinstance(data, TabsContainer):
@@ -262,9 +216,62 @@
                     self._current_category = name #XXX: awful hack because params need category and we don't keep parent
                 tab_cont = data
                 new_tab = tab_cont._xmluiAddTab(label or name)
-                self._parseChilds(new_tab, node, ['layout'])
+                self._parseChilds(new_tab, node, ('widget', 'container'))
+
+            elif node.nodeName == "widget":
+                id_ = node.getAttribute("id")
+                name = node.getAttribute("name")
+                type_ = node.getAttribute("type")
+                value = node.getAttribute("value") if node.hasAttribute('value') else u''
+                if type_=="empty":
+                    ctrl = self.widget_factory.createEmptyWidget(parent)
+                elif type_=="text":
+                    try:
+                        value = node.childNodes[0].wholeText
+                    except IndexError:
+                        warning (_("text node has no child !"))
+                    ctrl = self.widget_factory.createTextWidget(parent, value)
+                elif type_=="label":
+                    ctrl = self.widget_factory.createTextWidget(parent, value+": ")
+                elif type_=="string":
+                    ctrl = self.widget_factory.createStringWidget(parent, value)
+                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_=="password":
+                    ctrl = self.widget_factory.createPasswordWidget(parent, value)
+                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_=="textbox":
+                    ctrl = self.widget_factory.createTextBoxWidget(parent, value)
+                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_=="bool":
+                    ctrl = self.widget_factory.createBoolWidget(parent, value=='true')
+                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_=="list":
+                    style=[] if node.getAttribute("multi")=='yes' else ['single']
+                    _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")]
+                    ctrl = self.widget_factory.createListWidget(parent, _options, style)
+                    ctrl._xmluiSelectValue(node.getAttribute("value"))
+                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_=="button":
+                    callback_id = node.getAttribute("callback")
+                    ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress)
+                    ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in node.getElementsByTagName("field_back")])
+                else:
+                    error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_)
+                    raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
+
+                if self.type == 'param':
+                    try:
+                        ctrl._xmluiOnChange(self.onParamChange)
+                        ctrl._param_category = self._current_category
+                        ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1]
+                    except AttributeError:
+                        if not isinstance(ctrl, (EmptyWidget, TextWidget)):
+                            warning(_("No change listener on [%s]" % ctrl))
+
+                parent._xmluiAppend(ctrl)
+
             else:
-                raise NotImplementedError(_('Unknown tag'))
+                raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName)
 
     def constructUI(self, xml_data, post_treat=None):
         """ Actually construct the UI
@@ -272,8 +279,6 @@
         @param post_treat: frontend specific treatments to do once the UI is constructed
         @return: constructed widget
         """
-        ret_wid = self.widget_factory.createVerticalContainer(self)
-
         cat_dom = self.dom_parse(xml_data)
         top=cat_dom.documentElement
         self.type = top.getAttribute("type")
@@ -286,16 +291,13 @@
         if self.type == 'param':
             self.param_changed = set()
 
-        self._parseChilds(ret_wid, cat_dom.documentElement)
+        self._parseChilds(self, cat_dom.documentElement)
 
         if post_treat is not None:
-            ret_wid = post_treat(ret_wid)
+            post_treat()
 
         self.dom_free(cat_dom)
 
-        return ret_wid
-
-
     def _xmluiClose(self):
         """ Close the window/popup/... where the constructeur XMLUI is
         this method must be overrided