# HG changeset patch # User Goffi # Date 1355092129 -3600 # Node ID 03c22ddd7c94ef23865bb6c4aa758333fd49120c # Parent f6aeeb753c06821c45b05e3d65d3649bc0043a0e browser side: XMLUI implentation diff -r f6aeeb753c06 -r 03c22ddd7c94 browser_side/xmlui.py --- /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 + +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 . +""" + +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 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()