# HG changeset patch # User Goffi # Date 1391533355 -3600 # Node ID 46aa5ada61bfef352c0e624a8027c3fd4cb83484 # Parent 6625558371dbe36ce2cfda2b15e9f1a6f41cae44 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). diff -r 6625558371db -r 46aa5ada61bf frontends/src/constants.py --- a/frontends/src/constants.py Fri Jan 10 18:20:30 2014 +0100 +++ b/frontends/src/constants.py Tue Feb 04 18:02:35 2014 +0100 @@ -60,3 +60,7 @@ SYNTAX_CURRENT = "@CURRENT@" NO_SECURITY_LIMIT = -1 + + #XMLUI + SAT_FORM_PREFIX = "SAT_FORM_" + SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names diff -r 6625558371db -r 46aa5ada61bf frontends/src/primitivus/xmlui.py --- 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 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() diff -r 6625558371db -r 46aa5ada61bf frontends/src/tools/xml.py --- a/frontends/src/tools/xml.py Fri Jan 10 18:20:30 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# SAT: a jabber client -# Copyright (C) 2009, 2010, 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 . - -"""This library help manage XML used in SàT frontends """ - -# we don't import minidom as a different class can be used in frontends -# (e.g. NativeDOM in Libervia) - - -def inlineRoot(doc): - """ make the root attribute inline - @param root_node: minidom's Document compatible class - @return: plain XML - """ - root_elt = doc.documentElement - if root_elt.hasAttribute('style'): - styles_raw = root_elt.getAttribute('style') - styles = styles_raw.split(';') - new_styles = [] - for style in styles: - try: - key, value = style.split(':') - except ValueError: - continue - if key.strip().lower() == 'display': - value = 'inline' - new_styles.append('%s: %s' % (key.strip(), value.strip())) - root_elt.setAttribute('style', "; ".join(new_styles)) - else: - root_elt.setAttribute('style', 'display: inline') - return root_elt.toxml() - diff -r 6625558371db -r 46aa5ada61bf frontends/src/tools/xmltools.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/tools/xmltools.py Tue Feb 04 18:02:35 2014 +0100 @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# SAT: a jabber client +# Copyright (C) 2009, 2010, 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 . + +"""This library help manage XML used in SàT frontends """ + +# we don't import minidom as a different class can be used in frontends +# (e.g. NativeDOM in Libervia) + + +def inlineRoot(doc): + """ make the root attribute inline + @param root_node: minidom's Document compatible class + @return: plain XML + """ + root_elt = doc.documentElement + if root_elt.hasAttribute('style'): + styles_raw = root_elt.getAttribute('style') + styles = styles_raw.split(';') + new_styles = [] + for style in styles: + try: + key, value = style.split(':') + except ValueError: + continue + if key.strip().lower() == 'display': + value = 'inline' + new_styles.append('%s: %s' % (key.strip(), value.strip())) + root_elt.setAttribute('style', "; ".join(new_styles)) + else: + root_elt.setAttribute('style', 'display: inline') + return root_elt.toxml() + diff -r 6625558371db -r 46aa5ada61bf frontends/src/tools/xmlui.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/tools/xmlui.py Tue Feb 04 18:02:35 2014 +0100 @@ -0,0 +1,374 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# SàT frontend tools +# Copyright (C) 2009, 2010, 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 . + +from sat.core.i18n import _ +from sat_frontends.constants import Const +from logging import debug, info, warning, error + + +class InvalidXMLUI(Exception): + pass + + +def getText(node): + """Get child text nodes + @param node: dom Node + @return: joined unicode text of all nodes + + """ + data = [] + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + data.append(child.wholeText) + return u"".join(data) + + +class Widget(object): + """ base Widget """ + pass + + +class EmptyWidget(Widget): + """ Just a placeholder widget """ + pass + + +class TextWidget(Widget): + """ Non interactive text """ + pass + + +class StringWidget(Widget): + """ Input widget with require a string + often called Edit in toolkits + + """ + + +class PasswordWidget(Widget): + """ Input widget with require a masked string + + """ + + +class TextBoxWidget(Widget): + """ Input widget with require a long, possibly multilines string + often called TextArea in toolkits + + """ + + +class BoolWidget(Widget): + """ Input widget with require a boolean value + often called CheckBox in toolkits + + """ + + +class ButtonWidget(Widget): + """ A clickable widget """ + + +class ListWidget(Widget): + """ A widget able to show/choose one or several strings in a list """ + + +class AdvancedListWidget(Widget): + pass #TODO + +class Container(Widget): + """ Widget which can contain other ones with a specific layout """ + + @classmethod + def _xmluiAdapt(cls, instance): + """ Make cls as instance.__class__ + cls must inherit from original instance class + Usefull when you get a class from UI toolkit + + """ + assert instance.__class__ in cls.__bases__ + instance.__class__ = type(cls.__name__, cls.__bases__, dict(cls.__dict__)) + + +class PairsContainer(Container): + """ Widgets are disposed in rows of two (usually label/input) """ + pass + + +class TabsContainer(Container): + """ A container which several other containers in tabs + Often called Notebook in toolkits + + """ + + +class VerticalContainer(Container): + """ Widgets are disposed vertically """ + pass + + +class XMLUI(object): + """ Base class to construct SàT XML User Interface + New frontends can inherite this class to easily implement XMLUI + @property widget_factory: factory to create frontend-specific widgets + @proporety dialog_factory: factory to create frontend-specific dialogs + + """ + widget_factory = None + dialog_factory = None # TODO + + def __init__(self, host, xml_data, title = None, flags = None, dom_parse=None, dom_free=None): + """ Initialise the XMLUI instance + @param host: %(doc_host)s + @param xml_data: the raw XML containing the UI + @param title: force the title, or use XMLUI one if None + @param flags: list of string which can be: + - NO_CANCEL: the UI can't be cancelled + @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one + @param dom_free: method used to free the parsed DOM + + """ + if dom_parse is None: + from xml.dom import minidom + self.dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) + self.dom_free = lambda cat_dom: cat_dom.unlink() + else: + self.dom_parse = dom_parse + self.dom_free = dom_free or (lambda cat_dom: None) + self.host = host + self.title = title or "" + if flags is None: + flags = [] + self.flags = flags + self.ctrl_list = {} # usefull to access ctrl + self.constructUI(xml_data) + + def _parseElems(self, node, parent, post_treat=None): + """ Parse elements inside a tags, and add them to the parent + @param node: current XMLUI node + @param parent: parent container + @param post_treat: frontend specific treatments do to on each element + + """ + for elem in node.childNodes: + if elem.nodeName != "elem": + raise NotImplementedError(_('Unknown tag [%s]') % elem.nodeName) + 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 = self.widget_factory.createEmptyWidget(parent) + elif type_=="text": + try: + value = elem.childNodes[0].wholeText + except IndexError: + warning (_("text node has no child !")) + ctrl = self.widget_factory.createTextWidget(parent, value) + elif type_=="label": + ctrl = self.widget_factory.createTextWidget(parent, value+": ") + elif type_=="string": + ctrl = self.widget_factory.createStringWidget(parent, value) + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_=="password": + ctrl = self.widget_factory.createPasswordWidget(parent, value) + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_=="textbox": + ctrl = self.widget_factory.createTextBoxWidget(parent, value) + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_=="bool": + ctrl = self.widget_factory.createBoolWidget(parent, value=='true') + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_=="list": + style=[] if elem.getAttribute("multi")=='yes' else ['single'] + _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")] + ctrl = self.widget_factory.createListWidget(parent, _options, style) + ctrl._xmluiSelectValue(elem.getAttribute("value")) + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_=="button": + callback_id = elem.getAttribute("callback") + ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress) + ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")]) + elif type_=="advanced_list": + _options = [getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")] + ctrl = self.widget_factory.createListWidget(parent, _options, ['can_select_none']) + ctrl._xmluiSelectValue(elem.getAttribute("value")) + self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + else: + error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) + raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) + + if self.type == 'param': + try: + ctrl._xmluiOnChange(self.onParamChange) + ctrl._param_category = self._current_category + ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1] + except AttributeError: + if not isinstance(ctrl, (EmptyWidget, TextWidget)): + warning(_("No change listener on [%s]" % ctrl)) + + if post_treat is not None: + post_treat(ctrl, id_, name, type_, value) + parent._xmluiAppend(ctrl) + + def _parseChilds(self, current, elem, wanted = ['layout'], data = None): + """ Recursively parse childNodes of an elemen + @param current: widget container with '_xmluiAppend' 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 + @param data: additionnal data which are needed in some cases + + """ + 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 = self.widget_factory.createTabsContainer(current) + self._parseChilds(current, node, ['category'], tab_cont) + current._xmluiAppend(tab_cont) + elif type_ == "vertical": + self._parseElems(node, current) + elif type_ == "pairs": + pairs = self.widget_factory.createPairsContainer(current) + self._parseElems(node, pairs) + current._xmluiAppend(pairs) + else: + warning(_("Unknown layout [%s], using default one") % type_) + self._parseElems(node, current) + elif node.nodeName == "category": + name = node.getAttribute('name') + label = node.getAttribute('label') + if not name or not isinstance(data, 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 + new_tab = tab_cont._xmluiAddTab(label or name) + self._parseChilds(new_tab, node, ['layout']) + else: + raise NotImplementedError(_('Unknown tag')) + + def constructUI(self, xml_data, post_treat=None): + """ Actually construct the UI + @param xml_data: raw XMLUI + @param post_treat: frontend specific treatments to do once the UI is constructed + @return: constructed widget + """ + ret_wid = self.widget_factory.createVerticalContainer(self) + + cat_dom = self.dom_parse(xml_data) + top=cat_dom.documentElement + self.type = top.getAttribute("type") + self.title = self.title or top.getAttribute("title") or u"" + 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, cat_dom.documentElement) + + if post_treat is not None: + ret_wid = post_treat(ret_wid) + + self.dom_free(cat_dom) + + return ret_wid + + + def _xmluiClose(self): + """ Close the window/popup/... where the constructeur XMLUI is + this method must be overrided + + """ + raise NotImplementedError + + ##EVENTS## + + def onParamChange(self, ctrl): + """ Called when type is param and a widget to save is modified + @param ctrl: widget modified + + """ + assert(self.type == "param") + self.param_changed.add(ctrl) + + def onButtonPress(self, button): + """ Called when an XMLUI button is clicked + Launch the action associated to the button + @param button: the button clicked + + """ + callback_id, fields = button._xmlui_param_id + data = {} + for field in fields: + ctrl = self.ctrl_list[field] + if isinstance(ctrl['control'], ListWidget): + data[field] = u'\t'.join(ctrl['control']._xmluiGetSelected()) + else: + data[field] = ctrl['control']._xmluiGetValue() + self.host.launchAction(callback_id, data, profile_key = self.host.profile) + + def onFormSubmitted(self, ignore=None): + """ An XMLUI form has been submited + call the submit action associated with this form + + """ + selected_values = [] + for ctrl_name in self.ctrl_list: + escaped = u"%s%s" % (Const.SAT_FORM_PREFIX, ctrl_name) + ctrl = self.ctrl_list[ctrl_name] + if isinstance(ctrl['control'], ListWidget): + selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues()))) + else: + selected_values.append((escaped, ctrl['control']._xmluiGetValue())) + if 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._xmluiClose() + + def onFormCancelled(self, ignore=None): + """ Called when a form is cancelled """ + debug(_("Cancelling form")) + self._xmluiClose() + + + def onSaveParams(self, ignore=None): + """ Params are saved, we send them to backend + self.type must be param + + """ + assert(self.type == 'param') + for ctrl in self.param_changed: + if isinstance(ctrl, ListWidget): + value = u'\t'.join(ctrl._xmluiGetSelectedValues()) + else: + value = ctrl._xmluiGetValue() + self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category, + profile_key=self.host.profile) + self._xmluiClose() diff -r 6625558371db -r 46aa5ada61bf frontends/src/wix/main_window.py --- a/frontends/src/wix/main_window.py Fri Jan 10 18:20:30 2014 +0100 +++ b/frontends/src/wix/main_window.py Tue Feb 04 18:02:35 2014 +0100 @@ -24,7 +24,6 @@ import wx from sat_frontends.wix.contact_list import ContactList from sat_frontends.wix.chat import Chat -from sat_frontends.wix.param import Param from sat_frontends.wix.xmlui import XMLUI from sat_frontends.wix.gateways import GatewaysManager from sat_frontends.wix.profile import Profile @@ -56,7 +55,7 @@ def __init__(self): QuickApp.__init__(self) - wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500)) + wx.Frame.__init__(self,None, title="SàT Wix", size=(350,500)) #sizer self.sizer = wx.BoxSizer(wx.VERTICAL) @@ -407,7 +406,17 @@ def onParam(self, e): debug(_("Param request")) - param = Param(self) + def success(params): + XMLUI(self, xml_data=params, title=_("Configuration")) + + def failure(error): + dlg = wx.MessageDialog(self, unicode(error), + _('Error'), + wx.OK | wx.ICON_ERROR + ) + dlg.ShowModal() + dlg.Destroy() + self.bridge.getParamsUI(app=Const.APP_NAME, profile_key=self.profile, callback=success, errback=failure) def onAbout(self, e): about = wx.AboutDialogInfo() diff -r 6625558371db -r 46aa5ada61bf frontends/src/wix/param.py --- a/frontends/src/wix/param.py Fri Jan 10 18:20:30 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# wix: a SAT frontend -# Copyright (C) 2009, 2010, 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 . - - - -from sat.core.i18n import _ -import wx -import pdb -from xml.dom import minidom -from logging import debug, info, error -from sat.tools.jid import JID -from sat_frontends.wix.constants import Const - - -class Param(wx.Frame): - def __init__(self, host, title=_("Configuration")): - super(Param, self).__init__(None, title=title) - - self.host = host - - self.modified = {} # dict of modified data (i.e. what we have to save) - self.ctl_list = {} # usefull to access ctrl, key = (name, category) - - self.sizer = wx.BoxSizer(wx.VERTICAL) - self.notebook=wx.Notebook(self, -1, style=wx.NB_LEFT) - self.sizer.Add(self.notebook, 1, flag=wx.EXPAND) - self.SetSizer(self.sizer) - self.SetAutoLayout(True) - - #events - self.Bind(wx.EVT_CLOSE, self.onClose, self) - - self.MakeModal() - - for category in self.host.bridge.getParamsCategories(): - self.addCategory(category) - - self.Show() - - def addCategory(self, category): - panel=wx.Panel(self.notebook) - panel.sizer = wx.BoxSizer(wx.VERTICAL) - - def errorGettingParams(ignore): - wx.MessageDialog(self, _("Can't get parameters"), _("Parameters error"), wx.ICON_ERROR).ShowModal() - - def gotParams(result): - cat_dom = minidom.parseString(result.encode('utf-8')) - - for param in cat_dom.documentElement.getElementsByTagName("param"): - name = param.getAttribute("name") - label = param.getAttribute("label") - type = param.getAttribute("type") - value = param.getAttribute("value") - sizer = wx.BoxSizer(wx.HORIZONTAL) - if type=="string": - label=wx.StaticText(panel, -1, (label or name)+" ") - ctrl = wx.TextCtrl(panel, -1, value) - sizer.Add(label) - elif type=="password": - label=wx.StaticText(panel, -1, (label or name)+" ") - ctrl = wx.TextCtrl(panel, -1, value, style=wx.TE_PASSWORD) - sizer.Add(label) - elif type=="bool": - ctrl = wx.CheckBox(panel, -1, label or name, style = wx.CHK_2STATE) - ctrl.SetValue(value=="true") - elif type=="button": - ctrl = wx.Button(panel, -1, value) - ctrl.callback_id = param.getAttribute("callback_id") - else: - error(_("FIXME FIXME FIXME")) #FIXME ! - raise NotImplementedError - if name: - ctrl.param_id=(name, category) - self.ctl_list[(name, category)] = ctrl - sizer.Add(ctrl, 1, flag=wx.EXPAND) - panel.sizer.Add(sizer, flag=wx.EXPAND) - - if type=="string" or type=="password": - panel.Bind(wx.EVT_TEXT, self.onTextChanged, ctrl) - elif type=="bool": - panel.Bind(wx.EVT_CHECKBOX, self.onCheckBoxClicked, ctrl) - elif type=="button": - panel.Bind(wx.EVT_BUTTON, self.onButtonClicked, ctrl) - - panel.SetSizer(panel.sizer) - panel.SetAutoLayout(True) - self.notebook.AddPage(panel, category) - cat_dom.unlink() - - self.host.bridge.getParamsForCategory(category, - app=Const.APP_NAME, - profile_key=self.host.profile, - callback=gotParams, - errback=errorGettingParams) - - def onTextChanged(self, event): - """Called when a string paramater is modified""" - self.modified[event.GetEventObject().param_id]=event.GetString() - - ### FIXME # Some hacks for better presentation, should be generic # FIXME ### - if event.GetEventObject().param_id == ('JabberID', 'Connection'): - domain = JID(event.GetString()).domain - self.ctl_list[('Server', 'Connection')].SetValue(domain) - self.modified[('Server', 'Connection')] = domain - - event.Skip() - - def onCheckBoxClicked(self, event): - """Called when a bool paramater is modified""" - self.modified[event.GetEventObject().param_id]="true" if event.GetEventObject().GetValue() else "false" - event.Skip() - - def onButtonClicked(self, event): - """Called when a button paramater is modified""" - self.__save_parameters() - name, category = event.GetEventObject().param_id - callback_id = event.GetEventObject().callback_id - self.host.launchAction(callback_id, None, profile_key = self.host.profile) - event.Skip() - - def __save_parameters(self): - for param in self.modified: - self.host.bridge.setParam(param[0], self.modified[param], param[1], - profile_key=self.host.profile) - self.modified.clear() - - def onClose(self, event): - """Close event: we have to save the params.""" - debug(_("close")) - #now we save the modifier params - self.__save_parameters() - - self.MakeModal(False) - event.Skip() - diff -r 6625558371db -r 46aa5ada61bf frontends/src/wix/xmlui.py --- a/frontends/src/wix/xmlui.py Fri Jan 10 18:20:30 2014 +0100 +++ b/frontends/src/wix/xmlui.py Tue Feb 04 18:02:35 2014 +0100 @@ -21,239 +21,235 @@ from sat.core.i18n import _ import wx -import pdb -from xml.dom import minidom from logging import debug, info, warning, error from sat.tools.jid import JID +from sat_frontends.tools import xmlui -SAT_FORM_PREFIX = "SAT_FORM_" + +class EventWidget(object): + """ Used to manage change event of widgets """ + + def _xmluiOnChange(self, callback): + """ Call callback with widget as only argument """ + def change_cb(event): + callback(self) + self.Bind(self._xmlui_change_event, change_cb) + + +class WixWidget(object): + _xmlui_proportion = 0 + + +class ValueWidget(WixWidget): + def _xmluiGetValue(self): + return self.GetValue() + + +class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window): + + def __init__(self, parent): + wx.Window.__init__(self, parent, -1) + + +class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText): + + def __init__(self, parent, value): + wx.StaticText.__init__(self, parent, -1, value) + + +class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl): + _xmlui_change_event = wx.EVT_TEXT + + def __init__(self, parent, value): + wx.TextCtrl.__init__(self, parent, -1, value) + self._xmlui_proportion = 1 + + +class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl): + _xmlui_change_event = wx.EVT_TEXT + + def __init__(self, parent, value): + wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_PASSWORD) + self._xmlui_proportion = 1 + + +class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl): + _xmlui_change_event = wx.EVT_TEXT + + def __init__(self, parent, value): + wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_MULTILINE) + self._xmlui_proportion = 1 + + +class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox): + _xmlui_change_event = wx.EVT_CHECKBOX + + def __init__(self, parent, state): + wx.CheckBox.__init__(self, parent, -1, "", style=wx.CHK_2STATE) + self.SetValue(state) + self._xmlui_proportion = 1 + + def _xmluiGetValue(self): + return "true" if self.GetValue() else "false" -class XMLUI(wx.Frame): - """Create an user interface from a SàT xml""" +class ButtonWidget(EventWidget, WixWidget, xmlui.ButtonWidget, wx.Button): + _xmlui_change_event = wx.EVT_BUTTON + + def __init__(self, parent, value, click_callback): + wx.Button.__init__(self, parent, -1, value) + self._xmlui_click_callback = click_callback + parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self) + + def _xmluiOnClick(self, event): + self._xmlui_click_callback(event.GetEventObject()) + event.Skip() + +class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox): + _xmlui_change_event = wx.EVT_LISTBOX + + def __init__(self, parent, options, flags): + styles = wx.LB_MULTIPLE if not 'single' in flags else wx.LB_SINGLE + wx.ListBox.__init__(self, parent, -1, choices=[option[1] for option in options], style=styles) + self._xmlui_attr_map = {label: value for value, label in options} + self._xmlui_proportion = 1 + + def _xmluiSelectValue(self, value): + try: + label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0] + except IndexError: + warning(_("Can't find value [%s] to select" % value)) + return + for idx in xrange(self.GetCount()): + self.SetSelection(idx, self.GetString(idx) == label) + + def _xmluiGetSelectedValues(self): + ret = [] + labels = [self.GetString(idx) for idx in self.GetSelections()] + for label in labels: + ret.append(self._xmlui_attr_map[label]) + return ret + - def __init__(self, host, xml_data='', title="Form", options = None, misc = None): - if options is None: - options = [] - style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: Q&D tmp hack - super(XMLUI, self).__init__(None, title=title, style=style) +class AdvancedListWidget(ListWidget): + #TODO + + def __init__(self, parent, options, flags): + super(ListWidget, self).__init__(parent, options, flags) + + +class WixContainer(object): + _xmlui_proportion = 1 + + def _xmluiAppend(self, widget): + self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND) + + +class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel): - self.host = host - self.options = options - self.misc = misc or {} - self.ctrl_list = {} # usefull to access ctrl + def __init__(self, parent, weight_0='1', weight_1='1'): + wx.Panel.__init__(self, parent) + self.sizer = wx.FlexGridSizer(cols=2) + self.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs + self.SetSizer(self.sizer) + + +class TabsContainer(WixContainer, xmlui.TabsContainer, wx.Notebook): + + def __init__(self, parent): + wx.Notebook.__init__(self, parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0) + def _xmluiAddTab(self, label): + tab_panel = wx.Panel(self, -1) + tab_panel.sizer = wx.BoxSizer(wx.VERTICAL) + tab_panel.SetSizer(tab_panel.sizer) + self.AddPage(tab_panel, label) + VerticalContainer._xmluiAdapt(tab_panel) + return tab_panel + + +class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel): + + def __init__(self, parent): + wx.Panel.__init__(self, parent) self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) - self.SetAutoLayout(True) + + +class WidgetFactory(object): + + def __getattr__(self, attr): + if attr.startswith("create"): + cls = globals()[attr[6:]] + cls._xmlui_main = self._xmlui_main + return cls + + +class XMLUI(xmlui.XMLUI, wx.Frame, WixContainer): + """Create an user interface from a SàT XML""" + widget_factory = WidgetFactory() + + def __init__(self, host, xml_data, title=None, flags = None,): + self.widget_factory._xmlui_main = self + xmlui.XMLUI.__init__(self, host, xml_data, title, flags) + + def constructUI(self, xml_data): + style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in self.flags else wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, None, style=style) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.sizer) - #events - if not 'NO_CANCEL' in self.options: + def postTreat(ret_wid): + if self.title: + self.SetTitle(self.title) + + if self.type == 'form': + dialogButtons = wx.StdDialogButtonSizer() + submitButton = wx.Button(ret_wid,wx.ID_OK, label=_("Submit")) + dialogButtons.AddButton(submitButton) + ret_wid.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton) + if not 'NO_CANCEL' in self.flags: + cancelButton = wx.Button(ret_wid,wx.ID_CANCEL) + dialogButtons.AddButton(cancelButton) + ret_wid.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton) + dialogButtons.Realize() + ret_wid.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self._xmluiAppend(ret_wid) + self.sizer.Fit(self) + self.Show() + return ret_wid + + super(XMLUI, self).constructUI(xml_data, postTreat) + if not 'NO_CANCEL' in self.flags: self.Bind(wx.EVT_CLOSE, self.onClose, self) - self.MakeModal() - self.constructUI(xml_data) - - self.Show() - - def __parseElems(self, node, parent): - """Parse elements inside a tags, and add them to the parent sizer""" - for elem in node.childNodes: - if elem.nodeName != "elem": - message=_("Unmanaged tag") - error(message) - raise Exception(message) - _proportion = 0 - 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 = wx.Window(parent, -1) - elif type=="text": - try: - value = elem.childNodes[0].wholeText - except IndexError: - warning (_("text node has no child !")) - ctrl = wx.StaticText(parent, -1, value) - elif type=="label": - ctrl = wx.StaticText(parent, -1, value+": ") - elif type=="string": - ctrl = wx.TextCtrl(parent, -1, value) - self.ctrl_list[name] = ({'type':type, 'control':ctrl}) - _proportion = 1 - elif type=="password": - ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_PASSWORD) - self.ctrl_list[name] = ({'type':type, 'control':ctrl}) - _proportion = 1 - elif type=="textbox": - ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_MULTILINE) - self.ctrl_list[name] = ({'type':type, 'control':ctrl}) - _proportion = 1 - elif type=="bool": - ctrl = wx.CheckBox(panel, -1, "", style = wx.CHK_2STATE) - ctrl.SetValue(value=="true") - self.ctrl_list[name] = ({'type':type, 'control':ctrl}) - _proportion = 1 - elif type=="list": - style=wx.LB_MULTIPLE if elem.getAttribute("multi")=='yes' else wx.LB_SINGLE - _options = [(option.getAttribute("label"), option.getAttribute("value")) for option in elem.getElementsByTagName("option")] - attr_map = {label: value for label, value in _options} - ctrl = wx.ListBox(parent, -1, choices=[option[0] for option in _options], style=style) - self.ctrl_list[name] = ({'type':type, 'control':ctrl, 'attr_map': attr_map}) - _proportion = 1 - elif type=="button": - callback_id = elem.getAttribute("callback_id") - ctrl = wx.Button(parent, -1, value) - ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")]) - parent.Bind(wx.EVT_BUTTON, self.onButtonClicked, ctrl) - else: - error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME ! - raise NotImplementedError - parent.sizer.Add(ctrl, _proportion, flag=wx.EXPAND) - - def __parseChilds(self, parent, current_param, elem, wanted = ['layout']): - """Recursively parse childNodes of an elemen - @param parent: parent wx.Window - @param current_param: current wx.Window (often wx.Panel) or None if we must create one - @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 Exception("Invalid XMLUI") #TODO: make a custom exception - if node.nodeName == "layout": - _proportion = 0 - type = node.getAttribute('type') - if type == "tabs": - current = wx.Notebook(parent, -1, style=wx.NB_LEFT if self.type=='param' else 0) - self.__parseChilds(current, None, node, ['category']) - _proportion = 1 - else: - if current_param == None: - current = wx.Panel(parent, -1) - else: - current = current_param - if type == "vertical": - current.sizer = wx.BoxSizer(wx.VERTICAL) - elif type == "pairs": - current.sizer = wx.FlexGridSizer(cols=2) - current.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs - else: - warning(_("Unknown layout, using default one")) - current.sizer = wx.BoxSizer(wx.VERTICAL) - current.SetSizer(current.sizer) - self.__parseElems(node, current) - if parent: - parent.sizer.Add(current, _proportion, flag=wx.EXPAND) - elif node.nodeName == "category": - name = node.getAttribute('name') - label = node.getAttribute('label') - if not node.nodeName in wanted or not name or not isinstance(parent,wx.Notebook): - raise Exception("Invalid XMLUI") #TODO: make a custom exception - notebook = parent - tab_panel = wx.Panel(notebook, -1) - tab_panel.sizer = wx.BoxSizer(wx.VERTICAL) - tab_panel.SetSizer(tab_panel.sizer) - notebook.AddPage(tab_panel, label or name) - self.__parseChilds(tab_panel, None, node, ['layout']) - - else: - message=_("Unknown tag") - error(message) - raise Exception(message) #TODO: raise a custom exception here - - - def constructUI(self, xml_data): - panel=wx.Panel(self) - panel.sizer = wx.BoxSizer(wx.VERTICAL) - - cat_dom = minidom.parseString(xml_data.encode('utf-8')) - top= cat_dom.documentElement - self.type = top.getAttribute("type") - self.title = top .getAttribute("title") - self.session_id = top.getAttribute("session_id") or None - self.submit_id = top.getAttribute("submit") or None - if self.title: - self.SetTitle(self.title) - if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']: - raise Exception("Invalid XMLUI") #TODO: make a custom exception - - self.__parseChilds(panel, None, cat_dom.documentElement) - - if self.type == 'form': - dialogButtons = wx.StdDialogButtonSizer() - submitButton = wx.Button(panel,wx.ID_OK, label=_("Submit")) - dialogButtons.AddButton(submitButton) - panel.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton) - if not 'NO_CANCEL' in self.options: - cancelButton = wx.Button(panel,wx.ID_CANCEL) - dialogButtons.AddButton(cancelButton) - panel.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton) - dialogButtons.Realize() - panel.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL) - - panel.SetSizer(panel.sizer) - panel.SetAutoLayout(True) - panel.sizer.Fit(self) - self.sizer.Add(panel, 1, flag=wx.EXPAND) - cat_dom.unlink() + def _xmluiClose(self): + self.MakeModal(False) + self.Destroy() ###events - def onButtonClicked(self, event): - """Called when a button is pushed""" - callback_id, fields = event.GetEventObject().param_id - for field in fields: - ctrl = self.ctrl_list[field] - if isinstance(ctrl['control'], wx.ListBox): - data[field] = '\t'.join([ctrl['control'].GetString(idx) for idx in ctrl['control'].GetSelections()]) - else: - data[field] = ctrl['control'].GetValue() - - self.host.launchAction(callback_id, None, profile_key = self.host.profile) - event.Skip() + def onParamChange(self, ctrl): + super(XMLUI, self).onParamChange(ctrl) + ### FIXME # Some hacks for better presentation, should be generic # FIXME ### + if (ctrl._param_category, ctrl._param_name) == ('Connection', 'JabberID'): + domain = JID(ctrl._xmluiGetValue()).domain + for widget in (ctl['control'] for ctl in self.ctrl_list.values()): + if (widget._param_category, widget._param_name) == ('Connection', 'Server'): + widget.SetValue(domain) + break def onFormSubmitted(self, event): """Called when submit button is clicked""" - debug(_("Submitting form")) - 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'], wx.ListBox): - label = ctrl['control'].GetStringSelection() - value = ctrl['attr_map'][label] - selected_values.append((escaped, value)) - elif isinstance(ctrl['control'], wx.CheckBox): - selected_values.append((escaped, "true" if ctrl['control'].GetValue() else "false")) - else: - selected_values.append((escaped, ctrl['control'].GetValue())) - if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned - id = self.misc['action_back']("SUBMIT",self.misc['target'], selected_values) - self.host.current_action_ids.add(id) - elif self.misc.has_key('callback'): - self.misc['callback'](selected_values) - - 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.MakeModal(False) - self.Destroy() - - def onFormCancelled(self, event): - """Called when cancel button is clicked""" - debug(_("Cancelling form")) - self.MakeModal(False) - self.Close() + button = event.GetEventObject() + super(XMLUI, self).onFormSubmitted(button) def onClose(self, event): """Close event: we have to send the form.""" debug(_("close")) - self.MakeModal(False) + if self.type == 'param': + self.onSaveParams() event.Skip() diff -r 6625558371db -r 46aa5ada61bf src/tools/xml_tools.py --- a/src/tools/xml_tools.py Fri Jan 10 18:20:30 2014 +0100 +++ b/src/tools/xml_tools.py Tue Feb 04 18:02:35 2014 +0100 @@ -26,8 +26,8 @@ """This library help manage XML used in SàT (parameters, registration, etc) """ -SAT_FORM_PREFIX ="SAT_FORM_" - +SAT_FORM_PREFIX = "SAT_FORM_" +SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names def dataForm2XMLUI(form, submit_id, session_id=None): """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml""" @@ -152,16 +152,16 @@ assert(False) param_ui = XMLUI("param", "tabs") for category in top.getElementsByTagName("category"): - name = category.getAttribute('name') + category_name = category.getAttribute('name') label = category.getAttribute('label') - if not name: + if not category_name: error(_('INTERNAL ERROR: params categories must have a name')) assert(False) - param_ui.addCategory(name, 'pairs', label=label) + param_ui.addCategory(category_name, 'pairs', label=label) for param in category.getElementsByTagName("param"): - name = param.getAttribute('name') + param_name = param.getAttribute('name') label = param.getAttribute('label') - if not name: + if not param_name: error(_('INTERNAL ERROR: params must have a name')) assert(False) type_ = param.getAttribute('type') @@ -171,8 +171,8 @@ if type_ == "button": param_ui.addEmpty() else: - param_ui.addLabel(label or name) - param_ui.addElement(name=name, type_=type_, value=value, options=options, callback_id=callback_id) + param_ui.addLabel(label or param_name) + param_ui.addElement(name="%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name), type_=type_, value=value, options=options, callback_id=callback_id) return param_ui.toXml()