diff frontends/src/primitivus/xmlui.py @ 796:46aa5ada61bf

core, frontends: XMLUI refactoring: - now there is a base XMLUI class. Frontends inherits from this class, and add their specific widgets/containers/behaviour - wix: param.py has been removed, as the same behaviour has been reimplemented through XMLUI - removed "misc" argument in XMLUI.__init__ - severals things are broken (gateway, directory search), following patches will fix them - core: elements names in xml_tools.dataForm2XMLUI now use a construction with category_name/param_name to avoid name conflicts (could happen with 2 parameters of the same name in differents categories).
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:02:35 +0100
parents bfabeedbf32e
children 9007bb133009
line wrap: on
line diff
--- a/frontends/src/primitivus/xmlui.py	Fri Jan 10 18:20:30 2014 +0100
+++ b/frontends/src/primitivus/xmlui.py	Tue Feb 04 18:02:35 2014 +0100
@@ -22,26 +22,96 @@
 from urwid_satext import sat_widgets
 from logging import debug, info, warning, error
 from xml.dom import minidom
+from sat_frontends.tools import xmlui
+
+
+class PrimitivusEvents(object):
+    """ Used to manage change event of Primitivus widgets """
+
+    def _change_callback(self, ctrl, *args, **kwargs):
+        """" Call xmlui callback and ignore any extra argument """
+        args[-1](ctrl)
+
+    def _xmluiOnChange(self, callback):
+        """ Call callback with widget as only argument """
+        urwid.connect_signal(self, 'change', self._change_callback, callback)
+
+
+class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text):
+
+    def __init__(self, parent):
+        urwid.Text.__init__(self, '')
+
+
+class PrimitivusTextWidget(xmlui.TextWidget, urwid.Text):
+
+    def __init__(self, parent, value):
+        urwid.Text.__init__(self, value)
+
+
+class PrimitivusStringWidget(xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
+
+    def __init__(self, parent, value):
+        sat_widgets.AdvancedEdit.__init__(self, edit_text=value)
+
+    def _xmluiGetValue(self):
+        return self.get_edit_text()
+
+
+class PrimitivusPasswordWidget(xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents):
+
+    def __init__(self, parent, value):
+        sat_widgets.Password.__init__(self, edit_text=value)
+
+    def _xmluiGetValue(self):
+        return self.get_edit_text()
 
 
-SAT_FORM_PREFIX = "SAT_FORM_"
+class PrimitivusTextBoxWidget(xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
 
-def getText(node):
-    """Get child text nodes
-    @param node: dom Node
-    @return: joined unicode text of all nodes
+    def __init__(self, parent, value):
+        sat_widgets.AdvancedEdit.__init__(self, edit_text=value, multiline=True)
+
+    def _xmluiGetValue(self):
+        return self.getValue()
 
-    """
-    data = []
-    for child in node.childNodes:
-        if child.nodeType == child.TEXT_NODE:
-            data.append(child.wholeText)
-    return u"".join(data)
+
+class PrimitivusBoolWidget(xmlui.BoolWidget, urwid.CheckBox, PrimitivusEvents):
+
+    def __init__(self, parent, state):
+        urwid.CheckBox.__init__(self, '', state = state)
+
+    def _xmluiGetValue(self):
+        return "true" if self.get_state() else "false"
 
 
-class Pairs(urwid.WidgetWrap):
+class PrimitivusButtonWidget(xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents):
+
+    def __init__(self, parent, value, click_callback):
+        sat_widgets.CustomButton.__init__(self, value, on_press=click_callback)
+
+
+class PrimitivusListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents):
+
+    def __init__(self, parent, options, flags):
+        sat_widgets.List.__init__(self, options=options, style=flags)
+
+    def _xmluiSelectValue(self, value):
+        return self.selectValue(value)
 
-    def __init__(self, weight_0='1', weight_1='1'):
+    def _xmluiGetSelectedValues(self):
+        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'):
         self.idx = 0
         self.weight_0 = weight_0
         self.weight_1 = weight_1
@@ -49,7 +119,7 @@
         #XXX: empty Text hack needed because Pile doesn't support empty list
         urwid.WidgetWrap.__init__(self,columns)
 
-    def append(self, widget):
+    def _xmluiAppend(self, widget):
         pile = self._w.widget_list[self.idx]
         if isinstance(pile, urwid.Text):
             self._w.widget_list[self.idx] = urwid.Pile([widget])
@@ -59,162 +129,74 @@
             pile.contents.append((widget,('weight',getattr(self,'weight_'+str(self.idx)))))
         self.idx = (self.idx + 1) % 2
 
-class InvalidXMLUI(Exception):
-    pass
+
+class PrimitivusTabsContainer(xmlui.TabsContainer, sat_widgets.TabsContainer):
 
-class XMLUI(urwid.WidgetWrap):
+    def __init__(self, parent):
+        sat_widgets.TabsContainer.__init__(self)
+
+    def _xmluiAppend(self, widget):
+        self.body.append(widget)
 
-    def __init__(self, host, xml_data, title = None, options = None, misc = None):
-        self.host = host
-        self.title = title
-        self.options = options or []
-        self.misc = misc or {}
-        self.__dest = "window"
-        self.ctrl_list = {}  # usefull to access ctrl
-        widget = self.constructUI(xml_data)
-        urwid.WidgetWrap.__init__(self,widget)
+    def _xmluiAddTab(self, label):
+        list_box = super(PrimitivusTabsContainer, self).addTab(label)
+        if hasattr(PrimitivusVerticalContainer, "_PrimitivusVerticalContainer__super"): # workaround for Urwid's metaclass baviour
+                del PrimitivusVerticalContainer._PrimitivusVerticalContainer__super
+        PrimitivusVerticalContainer._xmluiAdapt(list_box)
+        return list_box
+
+
+class PrimitivusVerticalContainer(xmlui.VerticalContainer, urwid.ListBox):
+
 
-    def __parseElems(self, node, parent):
-        """Parse elements inside a <layout> tags, and add them to the parent"""
-        for elem in node.childNodes:
-            if elem.nodeName != "elem":
-                message=_("Unmanaged tag")
-                error(message)
-                raise Exception(message)
-            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 = urwid.Text('')
-            elif type_=="text":
-                try:
-                    value = elem.childNodes[0].wholeText
-                except IndexError:
-                    warning (_("text node has no child !"))
-                ctrl = urwid.Text(value)
-            elif type_=="label":
-                ctrl = urwid.Text(value+": ")
-            elif type_=="string":
-                ctrl = sat_widgets.AdvancedEdit(edit_text = value)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="password":
-                ctrl = sat_widgets.Password(edit_text = value)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="textbox":
-                ctrl = sat_widgets.AdvancedEdit(edit_text = value, multiline=True)
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="bool":
-                ctrl = urwid.CheckBox('', state = value=="true")
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="list":
-                style=[] if elem.getAttribute("multi")=='yes' else ['single']
-                ctrl = sat_widgets.List(options=[(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")], style=style, on_change=self.onParamChange if self.type == "param" else None)
-                ctrl.selectValue(elem.getAttribute("value"))
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            elif type_=="button":
-                callback_id = elem.getAttribute("callback")
-                ctrl = sat_widgets.CustomButton(value, on_press=self.onButtonPress)
-                ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
-            elif type_=="advanced_list":
-                ctrl = sat_widgets.List(options=[getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")], style=['can_select_none'], max_height=20, on_change=self.onParamChange)
-                ctrl.selectValue(elem.getAttribute("value"))
-                self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-            else:
-                error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)  #FIXME !
-                raise NotImplementedError
-            if self.type == 'param':
-                if isinstance(ctrl, urwid.Edit) or isinstance(ctrl, urwid.CheckBox):
-                    urwid.connect_signal(ctrl,'change',self.onParamChange)
-                elif isinstance(ctrl, sat_widgets.List):
-                    # the GenericList member triggers the events, not List itself
-                    # TODO: create a method GenericList.setParamData() for that,
-                    # or later in onSaveParams do something like ctrl.getParent()
-                    ctrl.genericList._param_category = self._current_category
-                    ctrl.genericList._param_name = name
-                ctrl._param_category = self._current_category
-                ctrl._param_name = name
-            parent.append(ctrl)
+    def __init__(self, parent):
+        urwid.ListBox.__init__(self, urwid.SimpleListWalker([]))
+
+    def _xmluiAppend(self, widget):
+        self.body.append(widget)
+
+
+class WidgetFactory(object):
 
-    def __parseChilds(self, current, elem, wanted = ['layout'], data = None):
-        """Recursively parse childNodes of an elemen
-        @param current: widget container with 'append' method
-        @param elem: 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"""
-        for node in elem.childNodes:
-            if wanted and not node.nodeName in wanted:
-                raise InvalidXMLUI
-            if node.nodeName == "layout":
-                type = node.getAttribute('type')
-                if type == "tabs":
-                    tab_cont = sat_widgets.TabsContainer()
-                    self.__parseChilds(current, node, ['category'], tab_cont)
-                    current.append(tab_cont)
-                elif type == "vertical":
-                    self.__parseElems(node, current)
-                elif type == "pairs":
-                    pairs = Pairs()
-                    self.__parseElems(node, pairs)
-                    current.append(pairs)
-                else:
-                    warning(_("Unknown layout, using default one"))
-                    self.__parseElems(node, current)
-            elif node.nodeName == "category":
-                name = node.getAttribute('name')
-                label = node.getAttribute('label')
-                if not name or not isinstance(data,sat_widgets.TabsContainer):
-                    raise InvalidXMLUI
-                if self.type == 'param':
-                    self._current_category = name #XXX: awful hack because params need category and we don't keep parent
-                tab_cont = data
-                listbox = tab_cont.addTab(label or name)
-                self.__parseChilds(listbox.body, node, ['layout'])
-            else:
-                message=_("Unknown tag")
-                error(message)
-                raise NotImplementedError
+    def __getattr__(self, attr):
+        if attr.startswith("create"):
+            return globals()["Primitivus" + attr[6:]] # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names
+
+
+class XMLUI(xmlui.XMLUI, urwid.WidgetWrap):
+    widget_factory = WidgetFactory()
+
+    def __init__(self, host, xml_data, title = None, flags = None):
+        self._dest = "window"
+        xmlui.XMLUI.__init__(self, host, xml_data, title, flags)
 
     def constructUI(self, xml_data):
-
-        ret_wid = urwid.ListBox(urwid.SimpleListWalker([]))
+        def postTreat(ret_wid):
+            assert ret_wid.body
 
-        cat_dom = minidom.parseString(xml_data.encode('utf-8'))
-        top=cat_dom.documentElement
-        self.type = top.getAttribute("type")
-        self.title = top.getAttribute("title") or self.title
-        self.session_id = top.getAttribute("session_id") or None
-        self.submit_id = top.getAttribute("submit") or None
-        if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']:
-            raise InvalidXMLUI
-
-        if self.type == 'param':
-            self.param_changed = set()
-
-        self.__parseChilds(ret_wid.body, cat_dom.documentElement)
-
-        assert ret_wid.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(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 self.type == 'form':
+                buttons = []
+                buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
+                if not 'NO_CANCEL' in self.flags:
+                    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)
+            elif self.type == 'param':
+                assert(isinstance(ret_wid,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
 
-        if self.type == 'form':
-            buttons = []
-            buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
-            if not 'NO_CANCEL' in self.options:
-                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)
-        elif self.type == 'param':
-            assert(isinstance(ret_wid,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
+        widget = super(XMLUI, self).constructUI(xml_data, postTreat)
+        urwid.WidgetWrap.__init__(self, widget)
 
     def show(self, show_type='popup', valign='middle'):
         """Show the constructed UI
@@ -223,8 +205,9 @@
             - window
         @param valign: vertical alignment when show_type is 'popup'.
                        Ignored when show_type is 'window'.
+
         """
-        self.__dest = "popup"
+        self._dest = "popup"
         decorated = sat_widgets.LabelLine(self, sat_widgets.SurroundedText(self.title or ''))
         if show_type == 'popup':
             self.host.showPopUp(decorated, valign=valign)
@@ -236,67 +219,8 @@
         self.host.redraw()
 
 
-    ##EVENTS##
-
-    def onButtonPress(self, button):
-        callback_id, fields = button.param_id
-        data = {}
-        for field in fields:
-            ctrl = self.ctrl_list[field]
-            if isinstance(ctrl['control'],sat_widgets.List):
-                data[field] = u'\t'.join(ctrl['control'].getSelectedValues())
-            else:
-                data[field] = ctrl['control'].getValue()
-
-        self.host.launchAction(callback_id, data, profile_key = self.host.profile)
-
-    def onParamChange(self, widget, extra=None):
-        """Called when type is param and a widget to save is modified"""
-        assert(self.type == "param")
-        self.param_changed.add(widget)
-
-    def onFormSubmitted(self, button):
-        selected_values = []
-        for ctrl_name in self.ctrl_list:
-            escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name)
-            ctrl = self.ctrl_list[ctrl_name]
-            if isinstance(ctrl['control'], sat_widgets.List):
-                selected_values.append((escaped, u'\t'.join([option.value for option in ctrl['control'].getSelectedValues()])))
-            elif isinstance(ctrl['control'], urwid.CheckBox):
-                selected_values.append((escaped, "true" if ctrl['control'].get_state() else "false"))
-            else:
-                selected_values.append((escaped, ctrl['control'].get_edit_text()))
-        if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
-            raise NotImplementedError
-        elif 'callback' in self.misc: # FIXME: this part is not needed anymore
-            try:
-                self.misc['callback'](selected_values, submit_id=self.submit_id, *self.misc['callback_args'])
-            except KeyError:
-                self.misc['callback'](selected_values, submit_id=self.submit_id)
-        elif self.submit_id is not None:
-            data = dict(selected_values)
-            if self.session_id is not None:
-                data["session_id"] = self.session_id
-            self.host.launchAction(self.submit_id, data, profile_key=self.host.profile)
-
-        else:
-            warning (_("The form data is not sent back, the type is not managed properly"))
-        self.host.removePopUp()
-
-    def onFormCancelled(self, button):
-        if self.__dest == 'window':
+    def _xmluiClose(self):
+        if self._dest == 'window':
             self.host.removeWindow()
         else:
             self.host.removePopUp()
-
-    def onSaveParams(self, button):
-        for ctrl in self.param_changed:
-            if isinstance(ctrl, urwid.CheckBox):
-                value = "true" if ctrl.get_state() else "false"
-            elif isinstance(ctrl, sat_widgets.GenericList):
-                value = u'\t'.join(ctrl.getSelectedValues())
-            else:
-                value = ctrl.get_edit_text()
-            self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category,
-                                      profile_key=self.host.profile)
-        self.host.removeWindow()