changeset 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 02ee9ef95277
children f100fd8d279f
files frontends/src/primitivus/xmlui.py frontends/src/tools/xmlui.py frontends/src/wix/main_window.py frontends/src/wix/xmlui.py src/memory/memory.py src/plugins/plugin_xep_0050.py src/plugins/plugin_xep_0055.py src/tools/xml_tools.py
diffstat 8 files changed, 589 insertions(+), 437 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/primitivus/xmlui.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/frontends/src/primitivus/xmlui.py	Tue Feb 04 18:19:00 2014 +0100
@@ -103,12 +103,6 @@
         return [option.value for option in self.getSelectedValues()]
 
 
-class PrimitivusAdvancedListWidget(PrimitivusListWidget, PrimitivusEvents):
-
-    def __init__(self, parent, options, flags):
-        sat_widgets.List.__init__(self, options=options, style=flags, max_height=20)
-
-
 class PrimitivusPairsContainer(xmlui.PairsContainer, urwid.WidgetWrap):
 
     def __init__(self, parent, weight_0='1', weight_1='1'):
@@ -148,7 +142,6 @@
 
 class PrimitivusVerticalContainer(xmlui.VerticalContainer, urwid.ListBox):
 
-
     def __init__(self, parent):
         urwid.ListBox.__init__(self, urwid.SimpleListWalker([]))
 
@@ -169,13 +162,14 @@
     def __init__(self, host, xml_data, title = None, flags = None):
         self._dest = "window"
         xmlui.XMLUI.__init__(self, host, xml_data, title, flags)
+        urwid.WidgetWrap.__init__(self, self.main_cont)
 
     def constructUI(self, xml_data):
-        def postTreat(ret_wid):
-            assert ret_wid.body
+        def postTreat():
+            assert self.main_cont.body
 
-            if isinstance(ret_wid.body[0],sat_widgets.TabsContainer):
-                ret_wid = ret_wid.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
+            if isinstance(self.main_cont.body[0],sat_widgets.TabsContainer):
+                self._main_cont = self.main_cont.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
 
             if self.type == 'form':
                 buttons = []
@@ -184,19 +178,18 @@
                     buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
                 max_len = max([len(button.get_label()) for button in buttons])
                 grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
-                ret_wid.body.append(grid_wid)
+                self.main_cont.body.append(grid_wid)
             elif self.type == 'param':
-                assert(isinstance(ret_wid,sat_widgets.TabsContainer))
+                assert(isinstance(self.main_cont,sat_widgets.TabsContainer))
                 buttons = []
                 buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
                 buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
                 max_len = max([button.getSize() for button in buttons])
                 grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
-                ret_wid.addFooter(grid_wid)
-            return ret_wid
+                self.main_cont.addFooter(grid_wid)
 
-        widget = super(XMLUI, self).constructUI(xml_data, postTreat)
-        urwid.WidgetWrap.__init__(self, widget)
+        super(XMLUI, self).constructUI(xml_data, postTreat)
+        urwid.WidgetWrap.__init__(self, self.main_cont)
 
     def show(self, show_type='popup', valign='middle'):
         """Show the constructed UI
--- 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
--- a/frontends/src/wix/main_window.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/frontends/src/wix/main_window.py	Tue Feb 04 18:19:00 2014 +0100
@@ -160,7 +160,7 @@
             help_string = self.bridge.getMenuHelp(id_, '')
             current_menu.Append(item_id, name, help=help_string)
             #now we register the event
-            def event_answer(e):
+            def event_answer(e, id_=id_):
                 self.launchAction(id_, None, profile_key = self.profile)
 
             wx.EVT_MENU(self, item_id, event_answer)
--- a/frontends/src/wix/xmlui.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/frontends/src/wix/xmlui.py	Tue Feb 04 18:19:00 2014 +0100
@@ -131,13 +131,6 @@
         return ret
 
 
-class AdvancedListWidget(ListWidget):
-    #TODO
-
-    def __init__(self, parent, options, flags):
-        super(ListWidget, self).__init__(parent, options, flags)
-
-
 class WixContainer(object):
     _xmlui_proportion = 1
 
@@ -185,7 +178,7 @@
             return cls
 
 
-class XMLUI(xmlui.XMLUI, wx.Frame, WixContainer):
+class XMLUI(xmlui.XMLUI, wx.Frame):
     """Create an user interface from a SàT XML"""
     widget_factory = WidgetFactory()
 
@@ -199,26 +192,25 @@
         self.sizer = wx.BoxSizer(wx.VERTICAL)
         self.SetSizer(self.sizer)
 
-        def postTreat(ret_wid):
+        def postTreat():
             if self.title:
                 self.SetTitle(self.title)
 
             if self.type == 'form':
                 dialogButtons = wx.StdDialogButtonSizer()
-                submitButton = wx.Button(ret_wid,wx.ID_OK, label=_("Submit"))
+                submitButton = wx.Button(self.main_cont,wx.ID_OK, label=_("Submit"))
                 dialogButtons.AddButton(submitButton)
-                ret_wid.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
+                self.main_cont.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
                 if not 'NO_CANCEL' in self.flags:
-                    cancelButton = wx.Button(ret_wid,wx.ID_CANCEL)
+                    cancelButton = wx.Button(self.main_cont,wx.ID_CANCEL)
                     dialogButtons.AddButton(cancelButton)
-                    ret_wid.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
+                    self.main_cont.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
                 dialogButtons.Realize()
-                ret_wid.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)
+                self.main_cont.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)
 
-            self._xmluiAppend(ret_wid)
+            self.sizer.Add(self.main_cont, 1, flag=wx.EXPAND)
             self.sizer.Fit(self)
             self.Show()
-            return ret_wid
 
         super(XMLUI, self).constructUI(xml_data, postTreat)
         if not 'NO_CANCEL' in self.flags:
@@ -251,5 +243,7 @@
         debug(_("close"))
         if self.type == 'param':
             self.onSaveParams()
+        else:
+            self._xmluiClose()
         event.Skip()
 
--- a/src/memory/memory.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/src/memory/memory.py	Tue Feb 04 18:19:00 2014 +0100
@@ -29,7 +29,7 @@
 from twisted.internet import defer, reactor
 from twisted.words.protocols.jabber import jid
 from twisted.python.failure import Failure
-from sat.tools.xml_tools import paramsXml2xmlUI
+from sat.tools.xml_tools import paramsXML2XMLUI
 from sat.core.default_config import default_config
 from sat.memory.sqlite import SqliteStorage
 from sat.memory.persistent import PersistentDict
@@ -130,7 +130,7 @@
             <param name="Priority" value="50" type="string" />
             <param name="Server" value="example.org" type="string" />
             <param name="Port" value="5222" type="string" />
-            <param name="NewAccount" value="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
+            <param name="NewAccount" label="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
             <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
             <param name="autodisconnect" label="%(label_autodisconnect)s" value="false"  type="bool" />
         </category>
@@ -621,7 +621,7 @@
             error(_("Asking params for inexistant profile"))
             return ""
         d = self.getParams(security_limit, app, profile)
-        return d.addCallback(lambda param_xml: paramsXml2xmlUI(param_xml))
+        return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml))
 
     def getParams(self, security_limit, app, profile_key):
         """Construct xml for asked profile, take params xml as skeleton
--- a/src/plugins/plugin_xep_0050.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/src/plugins/plugin_xep_0050.py	Tue Feb 04 18:19:00 2014 +0100
@@ -234,7 +234,7 @@
         form_ui.addText(_("Please select a command"), 'instructions')
 
         options = [(item.nodeIdentifier, item.name) for item in items]
-        form_ui.addList(options, "node")
+        form_ui.addList("node", options)
         return form_ui
 
     def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data):
@@ -329,7 +329,7 @@
         """
         form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
         form_ui.addText(_("Please enter target jid"), 'instructions')
-        form_ui.changeLayout("pairs")
+        form_ui.changeContainer("pairs")
         form_ui.addLabel("jid")
         form_ui.addString("jid")
         return {'xmlui': form_ui.toXml()}
--- a/src/plugins/plugin_xep_0055.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/src/plugins/plugin_xep_0055.py	Tue Feb 04 18:19:00 2014 +0100
@@ -75,7 +75,7 @@
         """
         form_ui = xml_tools.XMLUI("form", title=_("Search directory"), submit_id=self.__menu_cb_id)
         form_ui.addText(_("Please enter the search jid"), 'instructions')
-        form_ui.changeLayout("pairs")
+        form_ui.changeContainer("pairs")
         form_ui.addLabel("jid")
         form_ui.addString("jid", value="users.jabberfr.org") # TODO: replace users.jabberfr.org by any XEP-0055 compatible service discovered on current server
         return {'xmlui': form_ui.toXml()}
--- a/src/tools/xml_tools.py	Tue Feb 04 18:06:12 2014 +0100
+++ b/src/tools/xml_tools.py	Tue Feb 04 18:19:00 2014 +0100
@@ -29,9 +29,47 @@
 SAT_FORM_PREFIX = "SAT_FORM_"
 SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names
 
+
+# Helper functions
+
+def _dataFormField2XMLUIData(field):
+    """ Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field
+    @param field: data_form.Field (it uses field.value, field.fieldType, field.label and field.var)
+    @return: widget_type, widget_args, widget_kwargs
+
+    """
+    widget_args = [field.value]
+    widget_kwargs = {}
+    if field.fieldType == 'fixed' or field.fieldType is None:
+        widget_type = 'text'
+    elif field.fieldType == 'text-single':
+        widget_type = "string"
+    elif field.fieldType == 'text-private':
+        widget_type = "password"
+    elif field.fieldType == 'boolean':
+        widget_type = "bool"
+        if widget_args[0] is None:
+            widget_args[0] = 'false'
+    elif field.fieldType == 'list-single':
+        widget_type = "list"
+        del widget_args[0]
+        widget_kwargs["options"] = [option.value for option in field.options]
+    else:
+        error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
+        widget_type = "string"
+
+    if field.var:
+        widget_kwargs["name"] = field.var
+
+    return widget_type, widget_args, widget_kwargs
+
+
 def dataForm2XMLUI(form, submit_id, session_id=None):
-    """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml"""
+    """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT XML
+    @param submit_id: callback id to call when submitting form
+    @param session_id: id to return with the data
 
+    """
     form_ui = XMLUI("form", "vertical", submit_id=submit_id, session_id=session_id)
 
     if form.instructions:
@@ -40,32 +78,18 @@
     labels = [field for field in form.fieldList if field.label]
     if labels:
         # if there is no label, we don't need to use pairs
-        form_ui.changeLayout("pairs")
+        form_ui.changeContainer("pairs")
 
     for field in form.fieldList:
-        if field.fieldType == 'fixed':
-            __field_type = 'text'
-        elif field.fieldType == 'text-single':
-            __field_type = "string"
-        elif field.fieldType == 'text-private':
-            __field_type = "password"
-        elif field.fieldType == 'boolean':
-            __field_type = "bool"
-            if field.value is None:
-                field.value = "false"
-        elif field.fieldType == 'list-single':
-            __field_type = "list"
-        else:
-            error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
-            __field_type = "string"
-
+        widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field)
         if labels:
             if field.label:
                 form_ui.addLabel(field.label)
             else:
                 form_ui.addEmpty()
 
-        form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options])
+        form_ui.addWidget(widget_type, *widget_args, **widget_kwargs)
+
     return form_ui
 
 def dataFormResult2AdvancedList(form_ui, form_xml):
@@ -75,8 +99,7 @@
     @param form_xml: domish.Element of the data form
     @return: AdvancedList element
     """
-    headers = []
-    items = []
+    headers = {}
     try:
         reported_elt = form_xml.elements('jabber:x:data', 'reported').next()
     except StopIteration:
@@ -87,30 +110,28 @@
             raise exceptions.DataError("Unexpected tag")
         name = elt["var"]
         label = elt.attributes.get('label','')
-        type_ = elt.attributes.get('type','') # TODO
-        headers.append(Header(name, label))
+        type_ = elt.attributes.get('type')
+        headers[name] = (label, type_)
 
     if not headers:
         raise exceptions.DataError("No reported fields (see XEP-0004 §3.4)")
 
+    adv_list = AdvancedListContainer(form_ui, headers=headers, columns=len(headers), parent=form_ui.current_container)
+    form_ui.changeContainer(adv_list)
+
     item_elts = form_xml.elements('jabber:x:data', 'item')
 
     for item_elt in item_elts:
-        fields = []
         for elt in item_elt.elements():
             if elt.name != 'field':
                 warning("Unexpected tag (%s)" % elt.name)
                 continue
-            name = elt['var']
-            child_elt = elt.firstChildElement()
-            if child_elt.name != "value":
-                raise exceptions.DataError('Was expecting <value> tag')
-            value = unicode(child_elt)
-            fields.append(Field(name, value))
-        items.append(Item(' | '.join((field.value for field in fields if field)), fields))
+            field = data_form.Field.fromElement(elt)
 
-    return form_ui.addAdvancedList(None, headers, items)
+            widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field)
+            form_ui.addWidget(widget_type, *widget_args, **widget_kwargs)
 
+    return form_ui
 
 def dataFormResult2XMLUI(form_xml, session_id=None):
     """Take a raw data form (not parsed by XEP-0004) and convert it to a SàT XMLUI
@@ -147,41 +168,57 @@
 
     return form
 
-def paramsXml2xmlUI(xml):
+def paramsXML2XMLUI(xml):
     """Convert the xml for parameter to a SàT XML User Interface"""
     params_doc = minidom.parseString(xml.encode('utf-8'))
     top = params_doc.documentElement
     if top.nodeName != 'params':
-        error(_('INTERNAL ERROR: parameters xml not valid'))
-        assert(False)
+        raise exceptions.DataError(_('INTERNAL ERROR: parameters xml not valid'))
+
     param_ui = XMLUI("param", "tabs")
+    tabs_cont = param_ui.current_container
+
     for category in top.getElementsByTagName("category"):
         category_name = category.getAttribute('name')
         label = category.getAttribute('label')
         if not category_name:
-            error(_('INTERNAL ERROR: params categories must have a name'))
-            assert(False)
-        param_ui.addCategory(category_name, 'pairs', label=label)
+            raise exceptions.DataError(_('INTERNAL ERROR: params categories must have a name'))
+        tabs_cont.addTab(category_name, label=label, container=PairsContainer)
         for param in category.getElementsByTagName("param"):
+            widget_kwargs = {}
+
             param_name = param.getAttribute('name')
-            label = param.getAttribute('label')
+            param_label = param.getAttribute('label')
             if not param_name:
-                error(_('INTERNAL ERROR: params must have a name'))
-                assert(False)
+                raise exceptions.DataError(_('INTERNAL ERROR: params must have a name'))
+
             type_ = param.getAttribute('type')
             value = param.getAttribute('value') or None
-            options = getOptions(param)
             callback_id = param.getAttribute('callback_id') or None
+
+            if type_ == 'list':
+                options = _getParamListOptions(param)
+                widget_kwargs['options'] = options
+
             if type_ == "button":
                 param_ui.addEmpty()
             else:
-                param_ui.addLabel(label or param_name)
-            param_ui.addElement(name="%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name), type_=type_, value=value, options=options, callback_id=callback_id)
+                param_ui.addLabel(param_label or param_name)
+
+            if value:
+                widget_kwargs["value"] = value
+
+            if callback_id:
+                widget_kwargs['callback_id'] = callback_id
+
+            widget_kwargs['name'] = "%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name)
+
+            param_ui.addWidget(type_, **widget_kwargs)
 
     return param_ui.toXml()
 
 
-def getOptions(param):
+def _getParamListOptions(param):
     """Retrieve the options for list element. Allow listing the <option/>
     tags directly in <param/> or in an intermediate <options/> tag."""
     elems = param.getElementsByTagName("options")
@@ -194,60 +231,349 @@
     return [elem.getAttribute("value") for elem in elems]
 
 
-class Header(object):
-    """AdvandeList's header"""
+## XMLUI Elements
+
+
+class Element(object):
+    """ Base XMLUI element """
+    type = None
+
+    def __init__(self, xmlui, parent=None):
+        """Create a container element
+        @param xmlui: XMLUI instance
+        @parent: parent element
+        """
+        assert(self.type) is not None
+        if not hasattr(self, 'elem'):
+            self.elem = parent.xmlui.doc.createElement(self.type)
+        self.xmlui = xmlui
+        if parent is not None:
+            parent.append(self)
+        else:
+            self.parent = parent
+
+    def append(self, child):
+        self.elem.appendChild(child.elem)
+        child.parent = self
+
+
+class TopElement(Element):
+    """ Main XML Element """
+    type = 'top'
+
+    def __init__(self, xmlui):
+        self.elem = xmlui.doc.documentElement
+        super(TopElement, self).__init__(xmlui)
+
+
+class TabElement(Element):
+    """ Used by TabsContainer to give name and label to tabs """
+    type = 'tab'
 
-    def __init__(self, field_name, label, description=None, type_=None):
+    def __init__(self, parent, name, label):
+        if not isinstance(parent, TabsContainer):
+            raise exceptions.DataError(_("TabElement must be a child of TabsContainer"))
+        super(TabElement, self).__init__(parent.xmlui, parent)
+        self.elem.setAttribute('name', name)
+        self.elem.setAttribute('label', label)
+
+
+class FieldBackElement(Element):
+    """ Used by ButtonWidget to indicate which field have to be sent back """
+    type = 'field_back'
+
+    def __init__(self, parent, name):
+        assert(isinstance(parent, ButtonWidget))
+        super(FieldBackElement, self).__init__(parent.xmlui, parent)
+        self.elem.setAttribute('name', name)
+
+
+class OptionElement(Element):
+    """" Used by ListWidget to specify options """
+    type = 'option'
+
+    def __init__(self, parent, option):
+        assert(isinstance(parent, ListWidget))
+        super(OptionElement, self).__init__(parent.xmlui, parent)
+        if isinstance(option, basestring):
+            value, label = option, option
+        elif isinstance(option, tuple):
+            value, label = option
+        self.elem.setAttribute('value', value)
+        self.elem.setAttribute('label', label)
+
+
+class RowElement(Element):
+    """" Used by AdvancedListContainer """
+    type = 'row'
+
+    def __init__(self, parent):
+        assert(isinstance(parent, AdvancedListContainer))
+        super(RowElement, self).__init__(parent.xmlui, parent)
+
+
+class HeaderElement(Element):
+    """" Used by AdvancedListContainer """
+    type = 'header'
+
+    def __init__(self, parent, name=None, Label=None, description=None):
         """
-        @param field_name: name of the field referenced
+        @param parent: AdvancedListContainer instance
+        @param name: name of the container
         @param label: label to be displayed in columns
         @param description: long descriptive text
-        @param type_: TODO
+
+        """
+        assert(isinstance(parent, AdvancedListContainer))
+        super(HeaderElement, self).__init__(parent.xmlui, parent)
+        if name:
+            field_elt.setAttribute('name', name)
+        if label:
+            field_elt.setAttribute('label', label)
+        if description:
+            field_elt.setAttribute('description', description)
+
+
+class Container(Element):
+    """ And Element which contains other ones and has a layout """
+    type = None
+
+    def __init__(self, xmlui, parent=None):
+        """Create a container element
+        @param xmlui: XMLUI instance
+        @parent: parent element or None
+        """
+        self.elem = xmlui.doc.createElement('container')
+        super(Container, self).__init__(xmlui, parent)
+        self.elem.setAttribute('type', self.type)
+
+    def getParentContainer(self):
+        """ Return first parent container
+        @return: parent container or None
 
         """
-        self.field_name = field_name
-        self.label = label
-        self.description = description
-        self.type = type_
-        if type_ is not None:
-            raise NotImplementedError # TODO:
+        current = self.parent
+        while(not isinstance(current, (Container)) and
+              current is not None):
+            current = current.parent
+        return current
+
+class VerticalContainer(Container):
+    type = "vertical"
+
+
+class HorizontalContainer(Container):
+    type = "horizontal"
+
+
+class PairsContainer(Container):
+    type = "pairs"
+
+
+class TabsContainer(Container):
+    type = "tabs"
+
+    def addTab(self, name, label=None, container=VerticalContainer):
+        """Add a tab"""
+        if not label:
+            label = name
+        tab_elt = TabElement(self, name, label)
+        new_container = container(self.xmlui, tab_elt)
+        self.xmlui.changeContainer(new_container)
+
+    def end(self):
+        """ Called when we have finished tabs
+        change current container to first container parent
+
+        """
+        parent_container = self.getParentContainer()
+        self.xmlui.changeContainer(parent_container)
 
 
-class Item(object):
-    """Item used in AdvancedList"""
+class AdvancedListContainer(Container):
+    type = "advanced_list"
 
-    def __init__(self, text=None, fields=None):
+    def __init__(self, xmlui, name=None, headers=None, items=None, columns=None, parent=None):
+        """Create an advanced list
+        @param headers: optional headers informations
+        @param items: list of Item instances
+        @return: created element
         """
-        @param text: Optional textual representation, when fields are not showed individually
-        @param fields: list of Field instances²
+        if not items and columns is None:
+            raise DataError(_("either items or columns need do be filled"))
+        if headers is None:
+            headers = []
+        if items is None:
+            items = []
+        super(AdvancedListContainer, self).__init__(xmlui, parent)
+        if columns is None:
+            columns = len(items[0])
+        self._columns = columns
+        self._current_column = 0
+        self.current_row = None
+        if headers:
+            if len(headers) != self._columns:
+                raise exceptions.DataError(_("Headers lenght doesn't correspond to columns"))
+            self.addHeaders(headers)
+        if items:
+            self.addItems(items)
+
+    def addHeaders(self, headers):
+        for header in headers:
+            self.addHeader(header)
+
+    def addHeader(self, header):
+        pass # TODO
+
+    def addItems(self, items):
+        for item in items:
+            self.addItem(item)
+
+    def addItem(self, item):
+        if self._current_column % self._columns == 0:
+            self.current_row = RowElement(self)
+        self.current_row.append(item)
+
+    def end(self):
+        """ Called when we have finished list
+        change current container to first container parent
+
         """
-        self.text = text
-        self.fields = fields if fields is not None else []
+        if self._current_colum % self._columns != 0:
+            raise exceptions.DataError(_("Incorrect number of items in list"))
+        parent_container = self.getParentContainer()
+        self.xmlui.changeContainer(parent_container)
+
+
+class Widget(Element):
+    type = None
+
+    def __init__(self, xmlui, name=None, parent=None):
+        """Create an element
+        @param xmlui: XMLUI instance
+        @param name: name of the element or None
+        @param parent: parent element or None
+        """
+        self.elem = xmlui.doc.createElement('widget')
+        super(Widget, self).__init__(xmlui, parent)
+        if name:
+            self.elem.setAttribute('name', name)
+        self.elem.setAttribute('type', self.type)
+
+
+class InputWidget(Widget):
+    pass
+
+
+class EmptyWidget(Widget):
+    type = 'empty'
 
 
-class Field(object):
-    """Field used in AdvancedList (in items)"""
+class TextWidget(Widget):
+    type = 'text'
+
+    def __init__(self, xmlui, text, name=None, parent=None):
+        super(TextWidget, self).__init__(xmlui, name, parent)
+        text = self.xmlui.doc.createTextNode(text)
+        self.elem.appendChild(text)
+
+
+class LabelWidget(Widget):
+    type='label'
+
+    def __init__(self, xmlui, label, name=None, parent=None):
+        super(LabelWidget, self).__init__(xmlui, name, parent)
+        self.elem.setAttribute('value', label)
+
+
+class StringWidget(InputWidget):
+    type = 'string'
+
+    def __init__(self, xmlui, value=None, name=None, parent=None):
+        super(StringWidget, self).__init__(xmlui, name, parent)
+        if value:
+            self.elem.setAttribute('value', value)
+
+
+class PasswordWidget(StringWidget):
+    type = 'password'
 
-    def __init__(self, name, value):
+
+class TextBoxWidget(StringWidget):
+    type = 'textbox'
+
+
+class BoolWidget(InputWidget):
+    type = 'bool'
+
+    def __init__(self, xmlui, value='false', name=None, parent=None):
+        if value == '0':
+            value='false'
+        elif value == '1':
+            value='true'
+        if not value in ('true', 'false'):
+            raise exceptions.DataError(_("Value must be 0, 1, false or true"))
+        super(BoolWidget, self).__init__(xmlui, name, parent)
+        self.elem.setAttribute('value', value)
+
+
+class ButtonWidget(InputWidget):
+    type = 'button'
+
+    def __init__(self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None):
+        """Add a button
+        @param callback_id: callback which will be called if button is pressed
+        @param value: label of the button
+        @fields_back: list of names of field to give back when pushing the button
+        @param name: name
+        @param parent: parent container
         """
-        @param name: name of the field, used to identify the field in headers
-        @param value: actual content of the field
-        """
-        self.name = name
-        self.value = value
+        if fields_back is None:
+            fields_back = []
+        super(ButtonWidget, self).__init__(xmlui, name, parent)
+        self.elem.setAttribute('callback', callback_id)
+        if value:
+            self.elem.setAttribute('value', value)
+        for field in fields_back:
+            fback_el = FieldBackElement(self, field)
+
+
+class ListWidget(InputWidget):
+    type = 'list'
 
+    def __init__(self, xmlui, options, value=None, style=None, name=None, parent=None):
+        if style is None:
+            style = set()
+        styles = set(style)
+        assert options
+        if not styles.issubset(['multi']):
+            raise exceptions.DataError(_("invalid styles"))
+        super(ListWidget, self).__init__(xmlui, name, parent)
+        self.addOptions(options)
+        if value:
+            self.elem.setAttribute('value', value)
+        for style in styles:
+            self.elem.setAttribute(style, 'yes')
+
+    def addOptions(self, options):
+        """i Add options to a multi-values element (e.g. list) """
+        for option in options:
+            OptionElement(self, option)
+
+
+## XMLUI main class
 
 
 class XMLUI(object):
     """This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
 
-    def __init__(self, panel_type, layout="vertical", title=None, submit_id=None, session_id=None):
+    def __init__(self, panel_type, container="vertical", title=None, submit_id=None, session_id=None):
         """Init SàT XML Panel
         @param panel_type: one of
             - window (new window)
             - form (formulaire, depend of the frontend, usually a panel with cancel/submit buttons)
-            - param (parameters, presentatio depend of the frontend)
-        @param layout: disposition of elements, one of:
+            - param (parameters, presentation depend of the frontend)
+        @param container: disposition of elements, one of:
             - vertical: elements are disposed up to bottom
             - horizontal: elements are disposed left to right
             - pairs: elements come on two aligned columns
@@ -256,11 +582,14 @@
         @param title: title or default if None
         @param submit_id: callback id to call for panel_type we can submit (form, param)
         """
+        self._introspect()
         if panel_type not in ['window', 'form', 'param']:
             raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type)
         if panel_type == 'form' and submit_id is None:
             raise exceptions.DataError(_("form XMLUI need a submit_id"))
-        self.type_ = panel_type
+        if not isinstance(container, basestring):
+            raise exceptions.DataError(_("container argument must be a string"))
+        self.type = panel_type
         impl = minidom.getDOMImplementation()
 
         self.doc = impl.createDocument(None, "sat_xmlui", None)
@@ -270,14 +599,45 @@
             top_element.setAttribute("title", title)
         self.submit_id = submit_id
         self.session_id = session_id
-        self.parentTabsLayout = None  # used only we have 'tabs' layout
-        self.currentCategory = None  # used only we have 'tabs' layout
-        self.currentLayout = None
-        self.changeLayout(layout)
+        self.main_container  = self._createContainer(container, TopElement(self))
+        self.current_container = self.main_container
+
+    def _introspect(self):
+        """ Introspect module to find Widgets and Containers """
+        self._containers = {}
+        self._widgets = {}
+        for obj in globals().values():
+            try:
+                if issubclass(obj, Widget):
+                    if obj.__name__ == 'Widget':
+                        continue
+                    self._widgets[obj.type] = obj
+                elif issubclass(obj, Container):
+                    if obj.__name__ == 'Container':
+                        continue
+                    self._containers[obj.type] = obj
+            except TypeError:
+                pass
 
     def __del__(self):
         self.doc.unlink()
 
+    def __getattr__(self, name):
+        if name.startswith("add") and name not in ('addWidget',): # addWidgetName(...) create an instance of WidgetName
+            class_name = name[3:]+"Widget"
+            if class_name in globals():
+                cls = globals()[class_name]
+                if issubclass(cls, Widget):
+                    def createWidget(*args, **kwargs):
+                        if "parent" not in kwargs:
+                            kwargs["parent"] = self.current_container
+                        if "name" not in kwargs and issubclass(cls, InputWidget): # name can be given as first argument or in keyword arguments for InputWidgets
+                            args = list(args)
+                            kwargs["name"] = args.pop(0)
+                        return cls(self, *args, **kwargs)
+                    return createWidget
+        return object.__getattribute__(self, name)
+
     @property
     def submit_id(self):
         top_element = self.doc.documentElement
@@ -314,242 +674,45 @@
         else:
             raise exceptions.DataError("session_id can't be empty")
 
-    def _createLayout(self, layout, parent=None):
-        """Create a layout element
-        @param type: layout type (cf init doc)
+    def _createContainer(self, container, parent=None, **kwargs):
+        """Create a container element
+        @param type: container type (cf init doc)
         @parent: parent element or None
         """
-        if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']:
-            error(_("Unknown layout type [%s]") % layout)
-            assert False
-        layout_elt = self.doc.createElement('layout')
-        layout_elt.setAttribute('type', layout)
-        if parent is not None:
-            parent.appendChild(layout_elt)
-        return layout_elt
-
-    def _createElem(self, type_, name=None, parent=None):
-        """Create an element
-        @param type_: one of
-            - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout)
-            - text: text to be displayed in an multi-line area, e.g. instructions
-        @param name: name of the element or None
-        @param parent: parent element or None
-        @return: created element
-        """
-        elem = self.doc.createElement('elem')
-        if name:
-            elem.setAttribute('name', name)
-        elem.setAttribute('type', type_)
-        if parent is not None:
-            parent.appendChild(elem)
-        return elem
-
-    def changeLayout(self, layout):
-        """Change the current layout"""
-        self.currentLayout = self._createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement)
-        if layout == "tabs":
-            self.parentTabsLayout = self.currentLayout
-
-    def addEmpty(self, name=None):
-        """Add a multi-lines text"""
-        return self._createElem('empty', name, self.currentLayout)
-
-    def addText(self, text, name=None):
-        """Add a multi-lines text"""
-        elem = self._createElem('text', name, self.currentLayout)
-        text = self.doc.createTextNode(text)
-        elem.appendChild(text)
-        return elem
-
-    def addLabel(self, text, name=None):
-        """Add a single line text, mainly useful as label before element"""
-        elem = self._createElem('label', name, self.currentLayout)
-        elem.setAttribute('value', text)
-        return elem
-
-    def addString(self, name=None, value=None):
-        """Add a string box"""
-        elem = self._createElem('string', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-        return elem
-
-    def addPassword(self, name=None, value=None):
-        """Add a password box"""
-        elem = self._createElem('password', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-        return elem
-
-    def addTextBox(self, name=None, value=None):
-        """Add a string box"""
-        elem = self._createElem('textbox', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-        return elem
-
-    def addBool(self, name=None, value="true"):
-        """Add a string box"""
-        if value=="0":
-            value="false"
-        elif value=="1":
-            value="true"
-        assert value in ["true", "false"]
-        elem = self._createElem('bool', name, self.currentLayout)
-        elem.setAttribute('value', value)
-        return elem
-
-    def addList(self, options, name=None, value=None, style=None):
-        """Add a list of choices"""
-        if style is None:
-            style = set()
-        styles = set(style)
-        assert options
-        assert styles.issubset(['multi'])
-        elem = self._createElem('list', name, self.currentLayout)
-        self.addOptions(options, elem)
-        if value:
-            elem.setAttribute('value', value)
-        for style in styles:
-            elem.setAttribute(style, 'yes')
-        return elem
-
-    def addAdvancedList(self, name=None, headers=None, items=None):
-        """Create an advanced list
-        @param headers: optional headers informations
-        @param items: list of Item instances
-        @return: created element
-        """
-        elem = self._createElem('advanced_list', name, self.currentLayout)
-        self.addHeaders(headers, elem)
-        if items:
-            self.addItems(items, elem)
-        return elem
+        if container not in self._containers:
+            raise exceptions.DataError(_("Unknown container type [%s]") % container)
+        cls = self._containers[container]
+        new_container = cls(self, parent, **kwargs)
+        return new_container
 
-    def addButton(self, callback_id, name, value, fields_back=[]):
-        """Add a button
-        @param callback: callback which will be called if button is pressed
-        @param name: name
-        @param value: label of the button
-        @fields_back: list of names of field to give back when pushing the button
-        """
-        elem = self._createElem('button', name, self.currentLayout)
-        elem.setAttribute('callback', callback_id)
-        elem.setAttribute('value', value)
-        for field in fields_back:
-            fback_el = self.doc.createElement('field_back')
-            fback_el.setAttribute('name', field)
-            elem.appendChild(fback_el)
-        return elem
-
-    def addElement(self, type_, name=None, value=None, options=None, callback_id=None, headers=None, available=None):
-        """Convenience method to add element, the params correspond to the ones in addSomething methods"""
-        if type_ == 'empty':
-            return self.addEmpty(name)
-        elif type_ == 'text':
-            assert value is not None
-            return self.addText(value, name)
-        elif type_ == 'label':
-            assert(value)
-            return self.addLabel(value)
-        elif type_ == 'string':
-            return self.addString(name, value)
-        elif type_ == 'password':
-            return self.addPassword(name, value)
-        elif type_ == 'textbox':
-            return self.addTextBox(name, value)
-        elif type_ == 'bool':
-            if not value:
-                value = "true"
-            return self.addBool(name, value)
-        elif type_ == 'list':
-            return self.addList(options, name, value)
-        elif type_ == 'advancedlist':
-            return self.addAdvancedList(name, headers, available)
-        elif type_ == 'button':
-            assert(callback_id and value)
-            return self.addButton(callback_id, name, value)
-
-    # List
-
-    def addOptions(self, options, parent):
-        """Add options to a multi-values element (e.g. list)
-        @param parent: multi-values element"""
-        for option in options:
-            opt = self.doc.createElement('option')
-            if isinstance(option, basestring):
-                value, label = option, option
-            elif isinstance(option, tuple):
-                value, label = option
-            opt.setAttribute('value', value)
-            opt.setAttribute('label', label)
-            parent.appendChild(opt)
+    def changeContainer(self, container, **kwargs):
+        """Change the current container
+        @param container: either container type (container it then created),
+                          or an Container instance"""
+        if isinstance(container, basestring):
+            self.current_container = self._createContainer(container, self.current_container.getParentContainer() or self.main_container, **kwargs)
+        else:
+            self.current_container = self.main_container if container is None else container
+        assert(isinstance(self.current_container, Container))
+        return self.current_container
 
-    # Advanced list
-
-    def addHeaders(self, headers, parent):
-        headers_elt = self.doc.createElement('headers')
-        for header in headers:
-            field_elt = self.doc.createElement('field')
-            field_elt.setAttribute('field_name', header.field_name)
-            field_elt.setAttribute('label', header.label)
-            if header.description:
-                field_elt.setAttribute('description', header.description)
-            if header.type:
-                field_elt.setAttribute('type', header.type)
-            headers_elt.appendChild(field_elt)
-        parent.appendChild(headers_elt)
-
-    def addItems(self, items, parent):
-        """Add items to an AdvancedList
-        @param items: list of Item instances
-        @param parent: parent element (should be addAdvancedList)
-
-        """
-        items_elt = self.doc.createElement('items')
-        for item in items:
-            item_elt = self.doc.createElement('item')
-            if item.text is not None:
-                text_elt = self.doc.createElement('text')
-                text_elt.appendChild(self.doc.createTextNode(item.text))
-                item_elt.appendChild(text_elt)
-
-            for field in item.fields:
-                field_elt = self.doc.createElement('field')
-                field_elt.setAttribute('name', field.name)
-                field_elt.setAttribute('value', field.value)
-                item_elt.appendChild(field_elt)
-
-            items_elt.appendChild(item_elt)
-
-        parent.appendChild(items_elt)
-
-    # Tabs
-
-    def addCategory(self, name, layout, label=None):
-        """Add a category to current layout (must be a tabs layout)"""
-        assert(layout != 'tabs')
-        if not self.parentTabsLayout:
-            error(_("Trying to add a category without parent tabs layout"))
-            assert(False)
-        if self.parentTabsLayout.getAttribute('type') != 'tabs':
-            error(_("parent layout of a category is not tabs"))
-            assert(False)
-
-        if not label:
-            label = name
-        self.currentCategory = cat = self.doc.createElement('category')
-        cat.setAttribute('name', name)
-        cat.setAttribute('label', label)
-        self.changeLayout(layout)
-        self.parentTabsLayout.appendChild(cat)
+    def addWidget(self, type_, *args, **kwargs):
+        """Convenience method to add an element"""
+        if type_ not in self._widgets:
+            raise exceptions.DataError(_("Invalid type [%s]") % type_)
+        if "parent" not in kwargs:
+            kwargs["parent"] = self.current_container
+        cls = self._widgets[type_]
+        return cls(self, *args, **kwargs)
 
     def toXml(self):
         """return the XML representation of the panel"""
         return self.doc.toxml()
 
 
+# Misc other funtions
+
+
 class ElementParser(object):
     """callable class to parse XML string into Element
     Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942