diff frontends/src/wix/xmlui.py @ 796:46aa5ada61bf

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).
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:02:35 +0100
parents bfabeedbf32e
children 9007bb133009
line wrap: on
line diff
--- 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 <layout> 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()