Mercurial > libervia-backend
diff frontends/src/primitivus/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/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 <layout> 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()