changeset 335:e8c26e24a6c7

browser side: refactored XMLUI to use the new sat_frontends.tools.xmlui.XMLUI class, first draft
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 16:49:20 +0100
parents 9c6be29c714a
children 629c99bbd031
files browser_side/xmlui.py
diffstat 1 files changed, 296 insertions(+), 207 deletions(-) [+]
line wrap: on
line diff
--- a/browser_side/xmlui.py	Tue Feb 04 16:49:20 2014 +0100
+++ b/browser_side/xmlui.py	Tue Feb 04 16:49:20 2014 +0100
@@ -32,20 +32,186 @@
 from pyjamas.ui.CheckBox import CheckBox
 from pyjamas.ui.ListBox import ListBox
 from pyjamas.ui.Button import Button
+from pyjamas.ui.HTML import HTML
 from nativedom import NativeDOM
+from sat_frontends.tools import xmlui
+
+
+class EmptyWidget(xmlui.EmptyWidget, Label):
+
+    def __init__(self, parent):
+        Label.__init__(self, '')
+
+
+class TextWidget(xmlui.TextWidget, Label):
+
+    def __init__(self, parent, value):
+        Label.__init__(self, value)
+
+
+class LabelWidget(xmlui.LabelWidget, TextWidget):
+
+    def __init__(self, parent, value):
+        TextWidget.__init__(self, parent, value+": ")
+
+
+class JidWidget(xmlui.JidWidget, TextWidget):
+    pass
+
+
+class DividerWidget(xmlui.DividerWidget, HTML):
+
+    def __init__(self, parent, style='line'):
+        HTML.__init__(self, "<hr/>") # gof: TODO:
+
+
+class StringWidget(xmlui.StringWidget, TextBox):
+
+    def __init__(self, parent, value):
+        TextBox.__init__(self)
+        self.setText(value)
+
+    def _xmluiGetValue(self):
+        return self.getText()
+
+    def _xmluiOnChange(self, callback):
+        self.addhangeListener(callback)
+
+
+class PasswordWidget(xmlui.PasswordWidget, PasswordTextBox):
+
+    def __init__(self, parent, value):
+        TextBox.__init__(self)
+        self.setText(value)
+
+    def _xmluiGetValue(self):
+        return self.getText()
+
+    def _xmluiOnChange(self, callback):
+        self.addChangeListener(callback)
+
+
+class TextBoxWidget(xmlui.TextBoxWidget, TextArea):
+
+    def __init__(self, parent, value):
+        TextArea.__init__(self)
+        self.setText(value)
+
+    def _xmluiGetValue(self):
+        return self.getText()
+
+    def _xmluiOnChange(self, callback):
+        self.addChangeListener(callback)
+
+
+class BoolWidget(xmlui.BoolWidget, CheckBox):
+
+    def __init__(self, parent, state):
+        CheckBox.__init__(self)
+        self.setChecked(state)
+
+    def _xmluiGetValue(self):
+        return "true" if self.isChecked() else "false"
+
+    def _xmluiOnChange(self, callback):
+        self.addClickListener(callback)
 
 
-class InvalidXMLUI(Exception):
-    pass
+class ButtonWidget(xmlui.ButtonWidget, Button):
+
+    def __init__(self, parent, value, click_callback):
+        Button.__init__(self)
+
+    def _xmluiOnClick(self, event):
+        self.addClickListener(callback)
+
+
+class ListWidget(xmlui.ListWidget, ListBox):
+
+    def __init__(self, parent, options, flags):
+        ListBox.__init__(self)
+        self.setMultipleSelect('single' not in flags)
+        for option in options:
+            self.addItem(option[0])
+        self._xmlui_attr_map = {label: value for value, label in options}
+
+    def _xmluiSelectValue(self, value):
+        try:
+            label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
+        except IndexError:
+            print(_("WARNING: Can't find value [%s] to select" % value))
+            return
+        self.selectItem(label)
+
+    def _xmluiGetSelectedValues(self):
+        ret = []
+        for label in self.getSelectedItemText():
+            ret.append(self._xmlui_attr_map[label])
+        return ret
+
+    def _xmluiOnChange(self, callback):
+        self.addChangeListener(callback)
+
+
+class LiberviaContainer(object):
+
+    def _xmluiAppend(self, widget):
+        self.append(widget)
+
+
+class AdvancedListContainer(xmlui.AdvancedListContainer, Grid):
 
-class Pairs(Grid):
+    def __init__(self, parent, columns, selectable='no'):
+        Grid.__init_(self, 0, columns)
+        self.columns = columns
+        self.row = 0
+        self.col = 0
+        self._xmlui_rows_idx = []
+        self._xmlui_selectable = selectable != 'no'
+        self._xmlui_selected_row = None
+        self.addTableListener(self)
+
+    def onCellClicked(self, grid, row, col):
+        if not self._xmlui_selectable:
+            return
+        self._xmlui_selected_row = row
+        try:
+            self._xmlui_select_cb(self)
+        except AttributeError:
+            print "WARNING: no select callback set"
+
 
-    def __init__(self):
+    def _xmluiAppend(self, widget):
+        self.setWidget(self.row, self.col, widget)
+        self.col += 1
+
+    def _xmluiAddRow(self, idx):
+        self._xmlui_rows_idx.insert(self.row, idx)
+        self.row += 1
+        self.col = 0
+        self.resizeRows(self.row)
+
+    def _xmluiGetSelectedWidgets(self):
+        return [self.getWidget(self._xmlui_selected_row, col) for col in range(self.columns)]
+
+    def _xmluiGetSelectedIndex(self):
+        try:
+            return self._xmlui_rows_idx[self._xmlui_selected_row]
+        except TypeError:
+            return None
+
+    def _xmluiOnSelect(self, callback):
+        self._xmlui_select_cb = callback
+
+
+class PairsContainer(xmlui.PairsContainer, Grid):
+
+    def __init__(self, parent):
         Grid.__init__(self, 0, 0)
         self.row = 0
         self.col = 0
 
-    def append(self, widget):
+    def _xmluiAppend(self, widget):
         if self.col == 0:
             self.resize(self.row+1, 2)
         self.setWidget(self.row, self.col, widget)
@@ -54,228 +220,151 @@
             self.row +=1
             self.col = 0
 
-class XMLUI(VerticalPanel):
+
+
+class TabsContainer(LiberviaContainer, xmlui.TabsContainer, TabPanel):
+
+    def __init__(self, parent):
+        TabPanel.__init__(self)
+        self.setStyleName('liberviaTabPanel')
+
+    def _xmluiAddTab(self, label):
+        tab_panel = VerticalContainer(self)
+        self.add(tab_panel, label)
+        if len(self.getChildren()) == 1:
+            self.selectTab(0)
+        return tab_panel
+
+
+class VerticalContainer(LiberviaContainer, xmlui.VerticalContainer, VerticalPanel):
+    __bases__ = (LiberviaContainer, xmlui.VerticalContainer, VerticalPanel)
+
+    def __init__(self, parent):
+        VerticalPanel.__init__(self)
+
+
+class WidgetFactory(object):
+    # XXX: __getattr__ doesn't work here for an unknown reason
+
+    def createVerticalContainer(self, *args, **kwargs):
+        instance = VerticalContainer(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createPairsContainer(self, *args, **kwargs):
+        instance = PairsContainer(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createTabsContainer(self, *args, **kwargs):
+        instance = TabsContainer(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createEmptyWidget(self, *args, **kwargs):
+        instance = EmptyWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createTextWidget(self, *args, **kwargs):
+        instance = TextWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
 
-    def __init__(self, host, xml_data, title = None, options = None, misc = None, close_cb = None):
-        print "XMLUI init"
-        VerticalPanel.__init__(self)
+    def createLabelWidget(self, *args, **kwargs):
+        instance = LabelWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createJidWidget(self, *args, **kwargs):
+        instance = JidWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createDividerWidget(self, *args, **kwargs):
+        instance = DividerWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createStringWidget(self, *args, **kwargs):
+        instance = StringWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createPasswordWidget(self, *args, **kwargs):
+        instance = PasswordWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createTextBoxWidget(self, *args, **kwargs):
+        instance = TextBoxWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createBoolWidget(self, *args, **kwargs):
+        instance = BoolWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createButtonWidget(self, *args, **kwargs):
+        instance = ButtonWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+    def createListWidget(self, *args, **kwargs):
+        instance = ListWidget(*args, **kwargs)
+        instance._xmlui_main = self._xmlui_main
+        return instance
+
+
+    # def __getattr__(self, attr):
+    #     if attr.startswith("create"):
+    #         cls = globals()[attr[6:]]
+    #         cls._xmlui_main = self._xmlui_main
+    #         return cls
+
+
+class XMLUI(xmlui.XMLUI, VerticalPanel):
+    widget_factory = WidgetFactory()
+
+    def __init__(self, host, xml_data, title = None, flags = None):
+        self.widget_factory._xmlui_main = self
         self.dom = NativeDOM()
-        self.host = host
-        self.title = title
-        self.options = options or []
-        self.misc = misc or {}
-        self.close_cb = close_cb
-        self.__dest = "window"
-        self.ctrl_list = {}  # usefull to access ctrl
-        self.constructUI(xml_data)
+        dom_parse = lambda xml_data: self.dom.parseString(xml_data)
+        self._dest = "window"
+        VerticalPanel.__init__(self)
         self.setSize('100%', '100%')
+        xmlui.XMLUI.__init__(self, host, xml_data, title, flags, dom_parse)
 
     def setCloseCb(self, close_cb):
         self.close_cb = close_cb
 
-    def close(self):
+    def _xmluiClose(self):
         if self.close_cb:
             self.close_cb()
         else:
             print "WARNING: no close method defined"
 
-    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":
-                raise Exception("Unmanaged tag [%s]" % (elem.nodeName))
-            node_id = elem.getAttribute("node_id")
-            name = elem.getAttribute("name")
-            node_type = elem.getAttribute("type")
-            value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
-            if node_type=="empty":
-                ctrl = Label('')
-            elif node_type=="text":
-                try:
-                    value = elem.childNodes[0].wholeText
-                except IndexError:
-                    print ("WARNING: text node has no child !")
-                ctrl = Label(value)
-            elif node_type=="label":
-                ctrl = Label(value+": ")
-            elif node_type=="string":
-                ctrl = TextBox()
-                ctrl.setText(value)
-                self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl}
-            elif node_type=="password":
-                ctrl = PasswordTextBox()
-                ctrl.setText(value)
-                self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl}
-            elif node_type=="textbox":
-                ctrl = TextArea()
-                ctrl.setText(value)
-                self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl}
-            elif node_type=="bool":
-                ctrl = CheckBox()
-                ctrl.setChecked(value=="true")
-                self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl}
-            elif node_type=="list":
-                _options = [(option.getAttribute("label"), option.getAttribute("value")) for option in elem.getElementsByTagName("option")]
-                attr_map = {label: value for label, value in _options}
-                ctrl = ListBox()
-                ctrl.setMultipleSelect(elem.getAttribute("multi")=='yes')
-                for option in _options:
-                    ctrl.addItem(option[0])
-                ctrl.selectItem(value)
-                self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl, 'attr_map': attr_map}
-            elif node_type=="button":
-                callback_id = elem.getAttribute("callback_id")
-                ctrl = Button(value, self.onButtonPress)
-                ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
-            else:
-                print("FIXME FIXME FIXME: type [%s] is not implemented" % node_type)  #FIXME !
-                raise NotImplementedError
-            if self.node_type == 'param':
-                if isinstance(ctrl, TextBoxBase):
-                    ctrl.addChangeListener(self.onParamChange)
-                elif isinstance(ctrl, CheckBox):
-                    ctrl.addClickListener(self.onParamChange)
-                elif isinstance(ctrl, ListBox):
-                    ctrl.addChangeListener(self.onParamChange)
-                ctrl._param_category = self._current_category
-                ctrl._param_name = name
-            parent.append(ctrl)
-
-    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("ERROR: unexpected nodeName")
-            if node.nodeName == "layout":
-                node_type = node.getAttribute('type')
-                if node_type == "tabs":
-                    tab_cont = TabPanel()
-                    tab_cont.setStyleName('liberviaTabPanel')
-                    tab_cont.setHeight('100%')
-                    self.__parseChilds(current, node, ['category'], tab_cont)
-                    current.append(tab_cont)
-                    if isinstance(current, CellPanel):
-                        current.setCellHeight(tab_cont, '100%')
-                    if len(tab_cont.getChildren()) > 0:
-                        tab_cont.selectTab(0)
-                elif node_type == "vertical":
-                    self.__parseElems(node, current)
-                elif node_type == "pairs":
-                    pairs = Pairs()
-                    self.__parseElems(node, pairs)
-                    current.append(pairs)
-                else:
-                    print("WARNING: Unknown layout [%s], using default one" % (node_type,))
-                    self.__parseElems(node, current)
-            elif node.nodeName == "category":
-                name = node.getAttribute('name')
-                label = node.getAttribute('label')
-                if not name or not isinstance(data,TabPanel):
-                    raise InvalidXMLUI
-                if self.node_type == 'param':
-                    self._current_category = name #XXX: awful hack because params need category and we don't keep parent
-                tab_cont = data
-                tab_body = VerticalPanel()
-                tab_cont.add(tab_body, label or name)
-                self.__parseChilds(tab_body, node, ['layout'])
-            else:
-                message=_("Unknown tag")
-                raise NotImplementedError(message)
-
     def constructUI(self, xml_data):
-
-        cat_dom = self.dom.parseString(xml_data)
-
-        top=cat_dom.documentElement
-        self.node_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.node_type in ['form', 'param', 'window']:
-            raise InvalidXMLUI
-
-        if self.node_type == 'param':
-            self.param_changed = set()
-
-        self.__parseChilds(self, cat_dom.documentElement)
-
-        if self.node_type == 'form':
+        super(XMLUI, self).constructUI(xml_data)
+        self.add(self.main_cont)
+        self.setCellHeight(self.main_cont, '100%')
+        if self.type == 'form':
             hpanel = HorizontalPanel()
             hpanel.add(Button('Submit',self.onFormSubmitted))
-            if not 'NO_CANCEL' in self.options:
+            if not 'NO_CANCEL' in self.flags:
                 hpanel.add(Button('Cancel',self.onFormCancelled))
             self.add(hpanel)
-        elif self.node_type == 'param':
-            assert(isinstance(self.children[0],TabPanel))
+        elif self.type == 'param':
+            assert(isinstance(self.children[0][0],TabPanel))
             hpanel = HorizontalPanel()
-            hpanel.add(Button('Cancel', lambda ignore: self.close()))
+            hpanel.add(Button('Cancel', lambda ignore: self._xmluiClose()))
             hpanel.add(Button('Save', self.onSaveParams))
             self.add(hpanel)
 
     ##EVENTS##
 
-    def onButtonPress(self, button):
-        print "onButtonPress (%s)" % (button,)
-        callback_id, fields = button.param_id
-        for field in fields:
-            ctrl = self.ctrl_list[field]
-            if isinstance(ctrl['control'],ListBox):
-                data[field] = '\t'.join(ctrl['control'].getSelectedItemText())
-            elif isinstance(ctrl['control'],CheckBox):
-                data[field] =  "true" if ctrl['control'].isChecked() else "false"
-            else:
-                data[field] = ctrl['control'].getText()
-
-        self.host.launchAction(callback_id, None)
-
-    def onParamChange(self, widget):
-        """Called when type is param and a widget to save is modified"""
-        assert(self.node_type == "param")
-        print "onParamChange:", widget
-        self.param_changed.add(widget)
-
-    def onFormSubmitted(self, button):
-        print "onFormSubmitted"
-        # FIXME: untested
-        print "FIXME FIXME FIXME: Form submitting not managed yet"
-        data = []
-        for ctrl_name in self.ctrl_list:
-            escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name)
-            ctrl = self.ctrl_list[escaped]
-            if isinstance(ctrl['control'], ListBox):
-                data.append((escaped, '\t'.join([ctrl['attr_map'][label] for label in ctrl['control'].getSelectedItemText()])))
-            elif isinstance(ctrl['control'], CheckBox):
-                data.append((escaped, "true" if ctrl['control'].isChecked() else "false"))
-            else:
-                data.append((escaped, ctrl['control'].getText()))
-        if 'action_back' in self.misc: #FIXME FIXME FIXME: WTF ! Must be cleaned
-            raise NotImplementedError
-        elif 'callback' in self.misc:
-            self.misc['callback'](data)
-        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)
-        else:
-            print ("WARNING: The form data is not sent back, the type is not managed properly")
-
-        self.close()
-
-    def onFormCancelled(self, button):
-        self.close()
-
-    def onSaveParams(self, button):
-        print "onSaveParams"
-        for ctrl in self.param_changed:
-            if isinstance(ctrl, CheckBox):
-                value = "true" if ctrl.isChecked() else "false"
-            elif isinstance(ctrl, ListBox):
-                value = '\t'.join(ctrl.getSelectedItemText())
-            else:
-                value = ctrl.getText()
-            self.host.bridge.call('setParam', None, ctrl._param_name, value, ctrl._param_category)
-        self.close()
+    def onSaveParams(self, ignore=None):
+        def postTreat(name, value, category):
+            self.host.bridge.call('setParam', None, name, value, category)
+        xmlui.XMLUI.onSaveParams(self, ignore, postTreat)