# HG changeset patch # User Goffi # Date 1277223806 -28800 # Node ID 6be927a465ed0bd77d666a09b9bc93caa00ff860 # Parent 94011f553cd084cce51800686552f41790555da3 XMLUI refactoring, step 1 diff -r 94011f553cd0 -r 6be927a465ed frontends/wix/card_game.py --- a/frontends/wix/card_game.py Tue Jun 22 13:58:53 2010 +0800 +++ b/frontends/wix/card_game.py Wed Jun 23 00:23:26 2010 +0800 @@ -26,7 +26,7 @@ import pdb from logging import debug, info, error from tools.jid import JID -from form import Form +from xmlui import XMLUI CARD_WIDTH = 74 CARD_HEIGHT = 136 diff -r 94011f553cd0 -r 6be927a465ed frontends/wix/form.py --- a/frontends/wix/form.py Tue Jun 22 13:58:53 2010 +0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -""" -wix: a SAT frontend -Copyright (C) 2009, 2010 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 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" - - - -import wx -import pdb -from xml.dom import minidom -from logging import debug, info, warning, error -from tools.jid import JID - - -class Form(wx.Frame): - """Create a form from a SàT xml""" - - def __init__(self, host, xml_data='', title="Form", options=[], misc={}): - style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: gof: Q&D tmp hack - super(Form, self).__init__(None, title=title, style=style) - - self.host = host - self.options = options - self.misc = misc - self.ctl_list = [] # usefull to access ctrl - - self.sizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self.sizer) - self.SetAutoLayout(True) - - #events - if not 'NO_CANCEL' in self.options: - self.Bind(wx.EVT_CLOSE, self.onClose, self) - - self.MakeModal() - - self.constructForm(xml_data) - - self.Show() - - def constructForm(self, xml_data): - panel=wx.Panel(self) - panel.sizer = wx.BoxSizer(wx.VERTICAL) - - cat_dom = minidom.parseString(xml_data.encode('utf-8')) - - for elem in cat_dom.documentElement.getElementsByTagName("elem"): - name = elem.getAttribute("name") - label = elem.getAttribute("label") if elem.hasAttribute('label') else name - type = elem.getAttribute("type") - value = elem.getAttribute("value") if elem.hasAttribute('value') else u'' - sizer = wx.BoxSizer(wx.HORIZONTAL) - if type=="text": - ctrl = wx.StaticText(panel, -1, value) - elif type=="string": - label=wx.StaticText(panel, -1, label+": ") - ctrl = wx.TextCtrl(panel, -1, value) - self.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) - sizer.Add(label) - elif type=="password": - label=wx.StaticText(panel, -1, label+": ") - ctrl = wx.TextCtrl(panel, -1, value, style=wx.TE_PASSWORD) - self.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) - sizer.Add(label) - elif type=="list": - label=wx.StaticText(panel, -1, label+": ") - ctrl = wx.ListBox(panel, -1, choices=[option.getAttribute("value") for option in elem.getElementsByTagName("option")], style=wx.LB_SINGLE) - self.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) - else: - error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME ! - raise NotImplementedError - sizer.Add(ctrl, 1, flag=wx.EXPAND) - #self.ctl_list[(name, category)] = ctrl - panel.sizer.Add(sizer, flag=wx.EXPAND) - - 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 onFormSubmitted(self, event): - """Called when submit button is clicked""" - debug(_("Submitting form")) - data = [] - for ctrl in self.ctl_list: - if isinstance(ctrl['control'], wx.ListBox): - data.append((ctrl['name'], ctrl['control'].GetStringSelection())) - else: - data.append((ctrl["name"], 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'], data) - self.host.current_action_ids.add(id) - elif self.misc.has_key('callback'): - self.misc['callback'](data) - 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() - - def onClose(self, event): - """Close event: we have to send the form.""" - debug(_("close")) - self.MakeModal(False) - event.Skip() - diff -r 94011f553cd0 -r 6be927a465ed frontends/wix/main_window.py --- a/frontends/wix/main_window.py Tue Jun 22 13:58:53 2010 +0800 +++ b/frontends/wix/main_window.py Wed Jun 23 00:23:26 2010 +0800 @@ -27,7 +27,7 @@ from contact_list import ContactList from chat import Chat from param import Param -from form import Form +from xmlui import XMLUI from gateways import GatewaysManager from profile import Profile from profile_manager import ProfileManager @@ -293,17 +293,17 @@ ) dlg.ShowModal() dlg.Destroy() - elif type == "FORM": + elif type == "XMLUI": self.current_action_ids.remove(id) - debug (_("Form received")) + debug (_("XML user interface received")) misc = {} #FIXME FIXME FIXME: must clean all this crap ! title = _('Form') if data['type'] == _('registration'): - title = 'Registration' + title = _('Registration') misc['target'] = data['target'] misc['action_back'] = self.bridge.gatewayRegister - form=Form(self, title=title, xml_data = data['xml'], misc = misc) + XMLUI(self, title=title, xml_data = data['xml'], misc = misc) elif type == "RESULT": self.current_action_ids.remove(id) if self.current_action_ids_cb.has_key(id): diff -r 94011f553cd0 -r 6be927a465ed frontends/wix/xmlui.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/wix/xmlui.py Wed Jun 23 00:23:26 2010 +0800 @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +wix: a SAT frontend +Copyright (C) 2009, 2010 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 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + + +import wx +import pdb +from xml.dom import minidom +from logging import debug, info, warning, error +from tools.jid import JID + + +class XMLUI(wx.Frame): + """Create an user interface from a SàT xml""" + + def __init__(self, host, xml_data='', title="Form", options=[], misc={}): + style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: gof: Q&D tmp hack + super(XMLUI, self).__init__(None, title=title, style=style) + + self.host = host + self.options = options + self.misc = misc + self.ctl_list = [] # usefull to access ctrl + + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.sizer) + self.SetAutoLayout(True) + + #events + if not 'NO_CANCEL' in self.options: + self.Bind(wx.EVT_CLOSE, self.onClose, self) + + self.MakeModal() + + self.constructUI(xml_data) + + self.Show() + + def __parse_elems(self, childs, parent, sizer): + """Parse elements inside a tags, and add them to the sizer""" + for elem in childs: + if elem.nodeName != "elem": + message=_("Unmanaged tag") + error(message) + raise Exception(message) + _proportion = 0 + 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 KeyError: + 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.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) + _proportion = 1 + elif type=="password": + ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_PASSWORD) + self.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) + _proportion = 1 + elif type=="list": + ctrl = wx.ListBox(parent, -1, choices=[option.getAttribute("value") for option in elem.getElementsByTagName("option")], style=wx.LB_SINGLE) + self.ctl_list.append({'name':name, 'type':type, 'control':ctrl}) + _proportion = 1 + else: + error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME ! + raise NotImplementedError + sizer.Add(ctrl, _proportion, flag=wx.EXPAND) + + + + 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") + if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']: + message = _("XML UI received is invalid") + error(message) + raise Exception(message) + + for node in cat_dom.documentElement.childNodes: + if node.nodeName == "layout": + layout_panel = wx.Panel(panel, -1) + if node.getAttribute('type') == "vertical": + current_sizer = wx.BoxSizer(wx.VERTICAL) + elif node.getAttribute('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) + layout_panel.SetSizer(current_sizer) + self.__parse_elems(node.childNodes, layout_panel, current_sizer) + panel.sizer.Add(layout_panel, flag=wx.EXPAND) + else: + message=_("Unknown tag") + error(message) + raise Exception(message) #TODO: raise a custom exception here + + 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 onFormSubmitted(self, event): + """Called when submit button is clicked""" + debug(_("Submitting form")) + data = [] + for ctrl in self.ctl_list: + if isinstance(ctrl['control'], wx.ListBox): + data.append((ctrl['name'], ctrl['control'].GetStringSelection())) + else: + data.append((ctrl["name"], 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'], data) + self.host.current_action_ids.add(id) + elif self.misc.has_key('callback'): + self.misc['callback'](data) + 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() + + def onClose(self, event): + """Close event: we have to send the form.""" + debug(_("close")) + self.MakeModal(False) + event.Skip() + diff -r 94011f553cd0 -r 6be927a465ed plugins/plugin_misc_cs.py --- a/plugins/plugin_misc_cs.py Tue Jun 22 13:58:53 2010 +0800 +++ b/plugins/plugin_misc_cs.py Wed Jun 23 00:23:26 2010 +0800 @@ -105,7 +105,7 @@ """ % {'name':user_name, 'nb_message':unread_messages} - self.host.bridge.actionResult("FORM", id, {"type":"window", "xml":form_xml}) + self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":form_xml}) diff -r 94011f553cd0 -r 6be927a465ed plugins/plugin_xep_0077.py --- a/plugins/plugin_xep_0077.py Tue Jun 22 13:58:53 2010 +0800 +++ b/plugins/plugin_xep_0077.py Wed Jun 23 00:23:26 2010 +0800 @@ -68,7 +68,7 @@ form = data_form.Form.fromElement(x_elem) xml_data = dataForm2xml(form) - self.host.bridge.actionResult("FORM", answer['id'], {"target":answer["from"], "type":"registration", "xml":xml_data}) + self.host.bridge.actionResult("XMLUI", answer['id'], {"target":answer["from"], "type":"registration", "xml":xml_data}) def reg_err(self, failure): """Called when something is wrong with registration""" diff -r 94011f553cd0 -r 6be927a465ed sat.tac --- a/sat.tac Tue Jun 22 13:58:53 2010 +0800 +++ b/sat.tac Wed Jun 23 00:23:26 2010 +0800 @@ -659,7 +659,7 @@ def actionResult(self, id, type, data): """Send the result of an action @param id: same id used with action - @param type: result type ("PARAM", "SUCCESS", "ERROR", "FORM") + @param type: result type ("PARAM", "SUCCESS", "ERROR", "XMLUI") @param data: dictionary """ self.bridge.actionResult(type, id, data) diff -r 94011f553cd0 -r 6be927a465ed tools/xml_tools.py --- a/tools/xml_tools.py Tue Jun 22 13:58:53 2010 +0800 +++ b/tools/xml_tools.py Wed Jun 23 00:23:26 2010 +0800 @@ -30,19 +30,13 @@ def dataForm2xml(form): """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml""" - impl = minidom.getDOMImplementation() - - doc = impl.createDocument(None, "form", None) - top_element = doc.documentElement + form_panel = XMLUI("form", "vertical") - #result_xml = ["
", "
"] if form.instructions: - elem = doc.createElement('elem') - elem.setAttribute('name','instructions') - elem.setAttribute('type','text') - text = doc.createTextNode('\n'.join(form.instructions)) - elem.appendChild(text) - top_element.appendChild(elem) + form_panel.addText('\n'.join(form.instructions), 'instructions') + + form_panel.changeLayout("pairs") + for field in form.fieldList: if field.fieldType == 'fixed': __field_type = 'text' @@ -56,21 +50,13 @@ error (u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType) __field_type = "string" - elem = doc.createElement('elem') - if field.var: - elem.setAttribute('name', field.var) - elem.setAttribute('type', __field_type) - elem.setAttribute('label', field.label or "") - if field.value: - elem.setAttribute('value', field.value) - top_element.appendChild(elem) - for option in field.options: - opt = doc.createElement('option') - opt.setAttribute('value', option.value) - elem.appendChild(opt) - result = doc.toxml() - doc.unlink() - return result + if field.label: + form_panel.addLabel(field.label) + else: + form_panel.addEmpty() + + elem = form_panel.addElement(__field_type, field.var, None, field.value, [option.value for option in field.options]) + return form_panel.toXml() def tupleList2dataForm(values): """convert a list of tuples (name,value) to a wokkel submit data form""" @@ -80,6 +66,130 @@ form.addField(field) return form - + +class XMLUI: + """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" + + def __init__(self, panel_type, layout="vertical"): + """Init SàT XML Panel + @param panel_type: one of + - window (new window) + - form (formulaire, depend of the frontend, usually a panel with cancel/submit buttons) + - param (parameters, presentatio depend of the frontend) + @param layout: disposition of elements, one of: + - VERTICAL: elements are disposed up to bottom + - HORIZONTAL: elements are disposed left to right + - PAIRS: elements come on two aligned columns + (usually one for a label, the next for the element) + """ + if not panel_type in ['window', 'form', 'param']: + error(_("Unknown panel type [%s]") % panel_type) + assert(False) + self.type = panel_type + impl = minidom.getDOMImplementation() + + self.doc = impl.createDocument(None, "sat_xmlui", None) + top_element = self.doc.documentElement + top_element.setAttribute("type", panel_type) + self.currentLayout = self.__createLayout(layout, top_element) + + def __del__(self): + self.doc.unlink() + def __createLayout(self, layout, parent=None): + """Create a layout element + @param type: layout type (cf init doc) + @parent: parent element or None + """ + if not layout in ['vertical', 'horizontal', 'pairs']: + error (_("Unknown layout type [%s]") % layout) + assert (False) + layout_elt = self.doc.createElement('layout') + layout_elt.setAttribute('type',layout) + if parent != None: + parent.appendChild(layout_elt) + return layout_elt + def __createElem(self, type, name=None, parent = None): + """Create an element + @param type: one of + - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout) + - text: text to be displayed in an multi-line area, e.g. instructions + @param name: name of the element or None + @param parent: parent element or None + """ + elem = self.doc.createElement('elem') + if name: + elem.setAttribute('name', name) + elem.setAttribute('type', type) + if parent != None: + parent.appendChild(elem) + return elem + + def changeLayout(self, layout): + """Change the current layout""" + self.currentLayout = self.__createLayout(layout, self.doc.documentElement) + + + def addEmpty(self, name=None): + """Add a multi-lines text""" + elem = self.__createElem('empty', name, self.currentLayout) + + def addText(self, text, name=None): + """Add a multi-lines text""" + elem = self.__createElem('text', name, self.currentLayout) + text = self.doc.createTextNode(text) + elem.appendChild(text) + + def addLabel(self, text, name=None): + """Add a single line text, mainly useful as label before element""" + elem = self.__createElem('label', name, self.currentLayout) + elem.setAttribute('value', text) + + def addString(self, name=None, value=None): + """Add a string box""" + elem = self.__createElem('string', name, self.currentLayout) + if value: + elem.setAttribute('value', value) + + def addPassword(self, name=None, value=None): + """Add a password box""" + elem = self.__createElem('password', name, self.currentLayout) + if value: + elem.setAttribute('value', value) + + def addList(self, options, name=None, value=None): + """Add a list of choices""" + assert (options) + elem = self.__createElem('list', name, self.currentLayout) + self.addOptions(options, elem) + if value: + elem.setAttribute('value', value) + + def addElement(self, type, name = None, content = None, value = None, options = None): + """Convenience method to add element, the params correspond to the ones in addSomething methods""" + if type == 'empty': + self.addEmpty(name) + elif type == 'text': + assert(content) + self.addText(content, name) + elif type == 'label': + assert(value) + elif type == 'string': + self.addString(name, value) + elif type == 'password': + self.addPassword(name, value) + elif type == 'list': + self.addList(options, name, value) + + def addOptions(self, options, parent): + """Add options to a multi-values element (e.g. list) + @param parent: multi-values element""" + for option in options: + opt = self.doc.createElement('option') + opt.setAttribute('value', option) + parent.appendChild(opt) + + def toXml(self): + """return the XML representation of the panel""" + return self.doc.toxml()