view browser_side/xmlui.py @ 328:835a8ae799e7

Add notifications support, fixes bug 7.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 23 Feb 2013 16:27:32 +0100
parents d07b54fdc60a
children e8c26e24a6c7
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Libervia: a Salut à Toi frontend
Copyright (C) 2011, 2012, 2013 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":
                _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':
            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
        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()