changeset 143:03c22ddd7c94

browser side: XMLUI implentation
author Goffi <goffi@goffi.org>
date Sun, 09 Dec 2012 23:28:49 +0100
parents f6aeeb753c06
children 88512fbeb128
files browser_side/xmlui.py
diffstat 1 files changed, 266 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/browser_side/xmlui.py	Sun Dec 09 23:28:49 2012 +0100
@@ -0,0 +1,266 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Libervia: a Salut à Toi frontend
+Copyright (C) 2011, 2012  Jérôme Poisson <goffi@goffi.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from pyjamas.ui.VerticalPanel import VerticalPanel
+from pyjamas.ui.HorizontalPanel import HorizontalPanel
+from pyjamas.ui.CellPanel import CellPanel
+from pyjamas.ui.TabPanel import TabPanel
+from pyjamas.ui.Grid import Grid
+from pyjamas.ui.Label import Label
+from pyjamas.ui.TextBoxBase import TextBoxBase
+from pyjamas.ui.TextBox import TextBox
+from pyjamas.ui.PasswordTextBox import PasswordTextBox
+from pyjamas.ui.TextArea import TextArea
+from pyjamas.ui.CheckBox import CheckBox
+from pyjamas.ui.ListBox import ListBox
+from pyjamas.ui.Button import Button
+from nativedom import NativeDOM
+
+
+class InvalidXMLUI(Exception):
+    pass
+
+class Pairs(Grid):
+
+    def __init__(self):
+        Grid.__init__(self, 0, 0) 
+        self.row = 0
+        self.col = 0
+
+    def append(self, widget):
+        if self.col == 0:
+            self.resize(self.row+1, 2)
+        self.setWidget(self.row, self.col, widget)
+        self.col += 1
+        if self.col == 2:
+            self.row +=1
+            self.col = 0
+        
+class XMLUI(VerticalPanel):
+    
+    def __init__(self, host, xml_data, title = None, options = None, misc = None, close_cb = None):
+        print "XMLUI init"
+        VerticalPanel.__init__(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)
+        self.setSize('100%', '100%')
+
+    def setCloseCb(self, close_cb):
+        self.close_cb = close_cb
+
+    def close(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":
+                ctrl = ListBox()
+                ctrl.setMultipleSelect(elem.getAttribute("multi")=='yes')
+                for option in elem.getElementsByTagName("option"):
+                    ctrl.addItem(option.getAttribute("value"))
+                self.ctrl_list[name] = ({'node_type':node_type, 'control':ctrl})
+            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)
+                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%')
+                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
+        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':
+            hpanel = HorizontalPanel()
+            hpanel.add(Button('Submit',self.onFormSubmitted))
+            if not 'NO_CANCEL' in self.options:
+                hpanel.add(Button('Cancel',self.onFormCancelled))
+            self.add(hpanel)
+        elif self.node_type == 'param':
+            assert(isinstance(self.children[0],TabPanel))
+            hpanel = HorizontalPanel()
+            hpanel.add(Button('Cancel', lambda ignore: self.close())) 
+            hpanel.add(Button('Save', self.onSaveParams)) 
+            self.add(hpanel)
+
+    ##EVENTS##
+
+    def onButtonPress(self, button):
+        print "onButtonPress (%s)" % (button,)
+        callback_id, fields = button.param_id
+        data = {"callback_id":callback_id}
+        for field in fields:
+            ctrl = self.ctrl_list[field]
+            if isinstance(ctrl['control'],ListBox):
+                data[field] = '\t'.join(ctrl['control'].getSelectedValues())
+            elif isinstance(ctrl['control'],CheckBox):
+                data[field] =  "true" if ctrl['control'].isChecked() else "false"
+            else:
+                data[field] = ctrl['control'].getText()
+
+        self.host.bridge.call('launchAction', None, "button", data)
+        self.host.current_action_ids.add(id)
+
+    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:
+            ctrl = self.ctrl_list[ctrl_name]
+            if isinstance(ctrl['control'], ListBox):
+                data.append((ctrl_name, ctrl['control'].getValue()))
+            elif isinstance(ctrl['control'], CheckBox):
+                data.append((ctrl_name, "true" if ctrl['control'].isChecked() else "false"))
+            else:
+                data.append((ctrl_name, 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)
+        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"
+            else:
+                value = ctrl.getText()
+            self.host.bridge.call('setParam', None, ctrl._param_name, value, ctrl._param_category)
+        self.close()