From eaeb313e543a297c189d64543f5a842e154a4ce6 Mon Sep 17 00:00:00 2001 From: Goffi Date: Wed, 23 Jun 2010 00:23:26 +0800 Subject: [PATCH] XMLUI refactoring, step 1 --- frontends/wix/card_game.py | 2 +- frontends/wix/main_window.py | 10 +- frontends/wix/{form.py => xmlui.py} | 125 +++++++++++++-------- plugins/plugin_misc_cs.py | 2 +- plugins/plugin_xep_0077.py | 2 +- sat.tac | 2 +- tools/xml_tools.py | 166 +++++++++++++++++++++++----- 7 files changed, 228 insertions(+), 81 deletions(-) rename frontends/wix/{form.py => xmlui.py} (52%) diff --git a/frontends/wix/card_game.py b/frontends/wix/card_game.py index 2f7fa191..9c071682 100644 --- a/frontends/wix/card_game.py +++ b/frontends/wix/card_game.py @@ -26,7 +26,7 @@ import os.path, glob 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 --git a/frontends/wix/main_window.py b/frontends/wix/main_window.py index bca8c2c0..d5868e68 100644 --- a/frontends/wix/main_window.py +++ b/frontends/wix/main_window.py @@ -27,7 +27,7 @@ import wx 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 @@ class MainWindow(wx.Frame, QuickApp): ) 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 --git a/frontends/wix/form.py b/frontends/wix/xmlui.py similarity index 52% rename from frontends/wix/form.py rename to frontends/wix/xmlui.py index 229b8407..2ccaa159 100644 --- a/frontends/wix/form.py +++ b/frontends/wix/xmlui.py @@ -28,12 +28,12 @@ from logging import debug, info, warning, error from tools.jid import JID -class Form(wx.Frame): - """Create a form from a SàT xml""" +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(Form, self).__init__(None, title=title, style=style) + super(XMLUI, self).__init__(None, title=title, style=style) self.host = host self.options = options @@ -50,55 +50,92 @@ class Form(wx.Frame): self.MakeModal() - self.constructForm(xml_data) + self.constructUI(xml_data) self.Show() - def constructForm(self, xml_data): + 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')) - - 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) + 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) - 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) + 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) diff --git a/plugins/plugin_misc_cs.py b/plugins/plugin_misc_cs.py index ecf4a7bc..504c3069 100755 --- a/plugins/plugin_misc_cs.py +++ b/plugins/plugin_misc_cs.py @@ -105,7 +105,7 @@ class CS_Plugin(): """ % {'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 --git a/plugins/plugin_xep_0077.py b/plugins/plugin_xep_0077.py index 1f9488b2..e9a51bcd 100755 --- a/plugins/plugin_xep_0077.py +++ b/plugins/plugin_xep_0077.py @@ -68,7 +68,7 @@ class XEP_0077(): 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 --git a/sat.tac b/sat.tac index cf599311..96110c0e 100644 --- a/sat.tac +++ b/sat.tac @@ -659,7 +659,7 @@ class SAT(service.Service): 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 --git a/tools/xml_tools.py b/tools/xml_tools.py index 5ee7d692..e2e0a66c 100644 --- a/tools/xml_tools.py +++ b/tools/xml_tools.py @@ -30,19 +30,13 @@ import pdb 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 @@ def dataForm2xml(form): 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 @@ def tupleList2dataForm(values): 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()