Mercurial > libervia-backend
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()