# HG changeset patch # User souliane # Date 1396529776 -7200 # Node ID d2e612a45e76b5265542e8fb92ed8ee638f93a2f # Parent 68faf7d77a427151cf37a2e40ae4b0bc87dd383a tools, frontends (xmlui): add Widget.setInternalCallback: - to add UI callback not communication with the bridge - a list of predefined actions can be used to copy, move, select values - extra data can be passed within the 'internal_data' element TODO: frontend side operation to retrieve the data from node to data structure should be generic diff -r 68faf7d77a42 -r d2e612a45e76 frontends/src/tools/xmlui.py --- a/frontends/src/tools/xmlui.py Thu Apr 03 14:49:05 2014 +0200 +++ b/frontends/src/tools/xmlui.py Thu Apr 03 14:56:16 2014 +0200 @@ -191,7 +191,6 @@ raise ValueError(_("XMLUI can have only one main container")) self._main_cont = value - def _parseChilds(self, parent, current_node, wanted = ('container',), data = None): """ Recursively parse childNodes of an elemen @param parent: widget container with '_xmluiAppend' method @@ -315,14 +314,25 @@ print(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_) raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) - if self.type == 'param' and type_ != 'text': + if self.type == 'param' and type_ not in ('text', 'button'): try: ctrl._xmluiOnChange(self.onParamChange) ctrl._param_category = self._current_category - except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError if not isinstance(ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget)): print(_("No change listener on [%s]") % ctrl) + if type_ != 'text': + callback = node.getAttribute("internal_callback") or None + if callback: + fields = [field.getAttribute('name') for field in node.getElementsByTagName("internal_field")] + data = self.getInternalCallbackData(callback, node) + ctrl._xmlui_param_internal = (callback, fields, data) + if type_ == 'button': + ctrl._xmluiOnClick(self.onChangeInternal) + else: + ctrl._xmluiOnChange(self.onChangeInternal) + ctrl._xmlui_name = name parent._xmluiAppend(ctrl) @@ -403,6 +413,8 @@ """ callback_id, fields = button._xmlui_param_id + if not callback_id: # the button is probably bound to an internal action + return data = {} for field in fields: escaped = self.escape(field) @@ -413,6 +425,85 @@ data[escaped] = ctrl['control']._xmluiGetValue() self._xmluiLaunchAction(callback_id, data) + def onChangeInternal(self, ctrl): + """ Called when a widget that has been bound to an internal callback is changed. + + This is used to perform some UI actions without communicating with the backend. + See sat.tools.xml_tools.Widget.setInternalCallback for more details. + @param ctrl: widget modified + """ + action, fields, data = ctrl._xmlui_param_internal + if action not in ('copy', 'move', 'groups_of_contact'): + raise NotImplementedError(_("FIXME: XMLUI internal action [%s] is not implemented") % action) + + def copy_move(source, target): + """Depending of 'action' value, copy or move from source to target.""" + if isinstance(target, ListWidget): + if isinstance(source, ListWidget): + values = source._xmluiGetSelectedValues() + else: + values = [source._xmluiGetValue()] + if action == 'move': + source._xmluiSetValue('') + values = [value for value in values if value] + if values: + target._xmluiAddValues(values, select=True) + else: + if isinstance(source, ListWidget): + value = u', '.join(source._xmluiGetSelectedValues()) + else: + value = source._xmluiGetValue() + if action == 'move': + source._xmluiSetValue('') + target._xmluiSetValue(value) + + def groups_of_contact(source, target): + """Select in target the groups of the contact which is selected in source.""" + assert(isinstance(source, ListWidget)) + assert(isinstance(target, ListWidget)) + try: + contact_jid_s = source._xmluiGetSelectedValues()[0] + except IndexError: + return + target._xmluiSelectValues(data[contact_jid_s]) + pass + + source = None + for field in fields: + widget = self.ctrl_list[field]['control'] + if not source: + source = widget + continue + if action in ('copy', 'move'): + copy_move(source, widget) + elif action == 'groups_of_contact': + groups_of_contact(source, widget) + source = None + + def getInternalCallbackData(self, action, node): + """Retrieve from node the data needed to perform given action. + + TODO: it would be better to not have a specific way to retrieve + data for each action, but instead to have a generic method to + extract any kind of data structure from the 'internal_data' element. + + @param action (string): a value from the one that can be passed to the + 'callback' parameter of sat.tools.xml_tools.Widget.setInternalCallback + @param node (DOM Element): the node of the widget that triggers the callback + """ + try: # data is stored in the first 'internal_data' element of the node + data_elts = node.getElementsByTagName('internal_data')[0].childNodes + except IndexError: + return None + data = {} + if action == 'groups_of_contact': # return a dict(key: string, value: list[string]) + for elt in data_elts: + jid_s = elt.getAttribute('name') + data[jid_s] = [] + for value_elt in elt.childNodes: + data[jid_s].append(value_elt.getAttribute('name')) + return data + def onFormSubmitted(self, ignore=None): """ An XMLUI form has been submited call the submit action associated with this form diff -r 68faf7d77a42 -r d2e612a45e76 src/tools/xml_tools.py --- a/src/tools/xml_tools.py Thu Apr 03 14:49:05 2014 +0200 +++ b/src/tools/xml_tools.py Thu Apr 03 14:56:16 2014 +0200 @@ -24,6 +24,7 @@ from twisted.words.xish import domish from sat.core import exceptions + """This library help manage XML used in SàT (parameters, registration, etc) """ SAT_FORM_PREFIX = "SAT_FORM_" @@ -299,6 +300,26 @@ self.elem.setAttribute('name', name) +class InternalFieldElement(Element): + """ Used by internal callbacks to indicate which fields are manipulated """ + type = 'internal_field' + + def __init__(self, parent, name): + super(InternalFieldElement, self).__init__(parent.xmlui, parent) + self.elem.setAttribute('name', name) + + +class InternalDataElement(Element): + """ Used by internal callbacks to retrieve extra data """ + type = 'internal_data' + + def __init__(self, parent, children): + super(InternalDataElement, self).__init__(parent.xmlui, parent) + assert(isinstance(children, list)) + for child in children: + self.elem.childNodes.append(child) + + class OptionElement(Element): """" Used by ListWidget to specify options """ type = 'option' @@ -510,6 +531,32 @@ self.elem.setAttribute('name', name) self.elem.setAttribute('type', self.type) + def setInternalCallback(self, callback, fields, data_elts=None): + """Set an internal UI callback when the widget value is changed. + + The internal callbacks are NO callback ids, they are strings from + a predefined set of actions that are running in the scope of XMLUI. + + @param callback (string): a value from: + - 'copy': process the widgets given in 'fields' two by two, by + copying the values of one widget to the other. Target widgets + of type List do not accept the empty value. + - 'move': same than copy but moves the values if the source widget + is not a List. + - 'groups_of_contact': process the widgets two by two, assume A is + is a list of JID and B a list of groups, select in B the groups + to which the JID selected in A belongs. + - more operation to be added when necessary... + @param fields (list): a list of widget names (string) + @param data_elts (list[Element]): extra data elements + """ + self.elem.setAttribute('internal_callback', callback) + if fields: + for field in fields: + InternalFieldElement(self, field) + if data_elts: + InternalDataElement(self, data_elts) + class InputWidget(Widget): pass