# HG changeset patch # User Goffi # Date 1387894748 -3600 # Node ID 93bd868b8fb6412b2829251eefbe4ab148095749 # Parent 86224a13cc1d910e0b7fada8b5d60bf42a033479 backend, frontends: callbacks refactoring: - launchAction is now async, and return a dictionary for its result - no more action_id, actionResult* are deprecated - callback system is about to be unified diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/bridge/DBus.py --- a/frontends/src/bridge/DBus.py Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/bridge/DBus.py Tue Dec 24 15:19:08 2013 +0100 @@ -190,8 +190,8 @@ def isConnected(self, profile_key="@DEFAULT@"): return self.db_core_iface.isConnected(profile_key) - def launchAction(self, action_type, data, profile_key="@DEFAULT@"): - return unicode(self.db_core_iface.launchAction(action_type, data, profile_key)) + def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None): + return self.db_core_iface.launchAction(callback_id, data, profile_key, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:])) def registerNewAccount(self, login, password, email, host, port=5222): return unicode(self.db_core_iface.registerNewAccount(login, password, email, host, port)) diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/primitivus/primitivus --- a/frontends/src/primitivus/primitivus Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/primitivus/primitivus Tue Dec 24 15:19:08 2013 +0100 @@ -282,7 +282,6 @@ return input def _dynamicMenuCb(self, xmlui): - misc = {} ui = XMLUI(self, xml_data = xmlui) ui.show('popup') @@ -448,6 +447,29 @@ #No notification left, we can hide the bar self.main_widget.footer = self.editBar + def launchAction(self, callback_id, data=None, profile_key="@NONE@"): + """ Launch a dynamic action + @param callback_id: id of the action to launch + @param data: data needed only for certain actions + @param profile_key: %(doc_profile_key)s + + """ + if data is None: + data = dict() + def action_cb(data): + if not data: + # action was a one shot, nothing to do + pass + elif "xmlui" in data: + ui = XMLUI(self, xml_data = data['xmlui']) + ui.show('popup') + else: + self.showPopUp(sat_widgets.Alert(_("Error"), _(u"Unmanaged action result"), ok_cb=self.removePopUp)) + def action_eb(failure): + self.showPopUp(sat_widgets.Alert(_("Error"), unicode(failure), ok_cb=self.removePopUp)) + + self.bridge.launchAction(callback_id, data, profile_key, callback=action_cb, errback=action_eb) + def askConfirmation(self, confirmation_id, confirmation_type, data, profile): if not self.check_profile(profile): return diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/primitivus/xmlui.py --- a/frontends/src/primitivus/xmlui.py Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/primitivus/xmlui.py Tue Dec 24 15:19:08 2013 +0100 @@ -109,7 +109,7 @@ ctrl.selectValue(elem.getAttribute("value")) self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) elif type_=="button": - callback_id = elem.getAttribute("callback_id") + 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": @@ -178,6 +178,7 @@ top=cat_dom.documentElement self.type = top.getAttribute("type") self.title = top.getAttribute("title") or self.title + self.submit_id = top.getAttribute("submit") or None if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']: raise InvalidXMLUI @@ -234,7 +235,7 @@ def onButtonPress(self, button): callback_id, fields = button.param_id - data = {"callback_id":callback_id} + data = {} for field in fields: ctrl = self.ctrl_list[field] if isinstance(ctrl['control'],sat_widgets.List): @@ -242,8 +243,7 @@ else: data[field] = ctrl['control'].getValue() - id = self.host.bridge.launchAction("button", data, profile_key = self.host.profile) - self.host.current_action_ids.add(id) + 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""" @@ -262,12 +262,13 @@ data.append((ctrl_name, ctrl['control'].get_edit_text())) if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned raise NotImplementedError - self.host.debug() - elif 'callback' in self.misc: + elif 'callback' in self.misc: # FIXME: this part is not needed anymore try: - self.misc['callback'](data, *self.misc['callback_args']) + self.misc['callback'](data, submit_id=self.submit_id, *self.misc['callback_args']) except KeyError: - self.misc['callback'](data) + self.misc['callback'](data, submit_id=self.submit_id) + elif self.submit_id is not None: + self.host.launchAction(self.submit_id, dict(data), profile_key=self.host.profile) else: warning (_("The form data is not sent back, the type is not managed properly")) diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/wix/main_window.py --- a/frontends/src/wix/main_window.py Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/wix/main_window.py Tue Dec 24 15:19:08 2013 +0100 @@ -238,6 +238,39 @@ self.tools.Disable() return + def launchAction(self, callback_id, data=None, profile_key="@NONE@"): + """ Launch a dynamic action + @param callback_id: id of the action to launch + @param data: data needed only for certain actions + @param profile_key: %(doc_profile_key)s + + """ + if data is None: + data = dict() + def action_cb(data): + if not data: + # action was a one shot, nothing to do + pass + elif "xmlui" in data: + debug (_("XML user interface received")) + XMLUI(self, xml_data = data['xmlui']) + else: + dlg = wx.MessageDialog(self, _(u"Unmanaged action result"), + _('Error'), + wx.OK | wx.ICON_ERROR + ) + dlg.ShowModal() + dlg.Destroy() + def action_eb(failure): + dlg = wx.MessageDialog(self, unicode(failure), + _('Error'), + wx.OK | wx.ICON_ERROR + ) + dlg.ShowModal() + dlg.Destroy() + + self.bridge.launchAction(callback_id, data, profile_key, callback=action_cb, errback=action_eb) + def askConfirmation(self, confirmation_id, confirmation_type, data, profile): #TODO: refactor this in QuickApp if not self.check_profile(profile): diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/wix/param.py --- a/frontends/src/wix/param.py Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/wix/param.py Tue Dec 24 15:19:08 2013 +0100 @@ -129,9 +129,7 @@ self.__save_parameters() name, category = event.GetEventObject().param_id callback_id = event.GetEventObject().callback_id - data = {"name":name, "category":category, "callback_id":callback_id} - id = self.host.bridge.launchAction("button", data, profile_key = self.host.profile) - self.host.current_action_ids.add(id) + self.host.launchAction(callback_id, None, profile_key = self.host.profile) event.Skip() def __save_parameters(self): diff -r 86224a13cc1d -r 93bd868b8fb6 frontends/src/wix/xmlui.py --- a/frontends/src/wix/xmlui.py Tue Dec 24 15:19:08 2013 +0100 +++ b/frontends/src/wix/xmlui.py Tue Dec 24 15:19:08 2013 +0100 @@ -30,11 +30,13 @@ """Create an user interface from a SàT xml""" def __init__(self, host, xml_data='', title="Form", options = None, misc = None): - style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: gof: Q&D tmp hack + 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) self.host = host - self.options = options or [] + self.options = options self.misc = misc or {} self.ctrl_list = {} # usefull to access ctrl @@ -165,6 +167,7 @@ top= cat_dom.documentElement self.type = top.getAttribute("type") self.title = top .getAttribute("title") + 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']: @@ -195,7 +198,6 @@ def onButtonClicked(self, event): """Called when a button is pushed""" callback_id, fields = event.GetEventObject().param_id - data = {"callback_id":callback_id} for field in fields: ctrl = self.ctrl_list[field] if isinstance(ctrl['control'], wx.ListBox): @@ -203,8 +205,7 @@ else: data[field] = ctrl['control'].GetValue() - id = self.host.bridge.launchAction("button", data, profile_key = self.host.profile) - self.host.current_action_ids.add(id) + self.host.launchAction(callback_id, None, profile_key = self.host.profile) event.Skip() def onFormSubmitted(self, event): @@ -224,6 +225,10 @@ self.host.current_action_ids.add(id) elif self.misc.has_key('callback'): self.misc['callback'](data) + + elif self.submit_id is not None: + data = dict(selected_values) + 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) diff -r 86224a13cc1d -r 93bd868b8fb6 src/bridge/DBus.py --- a/src/bridge/DBus.py Tue Dec 24 15:19:08 2013 +0100 +++ b/src/bridge/DBus.py Tue Dec 24 15:19:08 2013 +0100 @@ -378,10 +378,10 @@ return self._callback("isConnected", unicode(profile_key)) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sa{ss}s', out_signature='s', - async_callbacks=None) - def launchAction(self, action_type, data, profile_key="@DEFAULT@"): - return self._callback("launchAction", unicode(action_type), data, unicode(profile_key)) + in_signature='sa{ss}s', out_signature='a{ss}', + async_callbacks=('callback', 'errback')) + def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None): + return self._callback("launchAction", unicode(callback_id), data, unicode(profile_key), callback=callback, errback=errback) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='ssssi', out_signature='s', diff -r 86224a13cc1d -r 93bd868b8fb6 src/bridge/bridge_constructor/bridge_template.ini --- a/src/bridge/bridge_constructor/bridge_template.ini Tue Dec 24 15:19:08 2013 +0100 +++ b/src/bridge/bridge_constructor/bridge_template.ini Tue Dec 24 15:19:08 2013 +0100 @@ -104,6 +104,7 @@ doc_param_1=%(doc_profile)s [askConfirmation] +deprecated= type=signal category=core sig_in=ssa{ss}s @@ -116,6 +117,7 @@ doc_param_3=%(doc_profile)s [actionResult] +deprecated= type=signal category=core sig_in=ssa{ss}s @@ -130,6 +132,7 @@ doc_param_3=%(doc_profile)s [actionResultExt] +deprecated= type=signal category=core sig_in=ssa{sa{ss}}s @@ -533,18 +536,21 @@ doc_param_1=%(doc_profile_key)s [launchAction] +async= type=method category=core sig_in=sa{ss}s -sig_out=s +sig_out=a{ss} param_2_default="@DEFAULT@" -doc=Launch a specific action -doc_param_0=action_type: type of the action which can be: - - button: A button is pushed -doc_param_1=data: action_type dependant data +doc=Launch a registred action +doc_param_0=callback_id: id of the registred callback +doc_param_1=data: optional data doc_param_2=%(doc_profile_key)s +doc_return=dict where key can be: + - xmlui: a XMLUI need to be displayed [confirmationAnswer] +deprecated= type=method category=core sig_in=sba{ss}s diff -r 86224a13cc1d -r 93bd868b8fb6 src/core/sat_main.py --- a/src/core/sat_main.py Tue Dec 24 15:19:08 2013 +0100 +++ b/src/core/sat_main.py Tue Dec 24 15:19:08 2013 +0100 @@ -42,6 +42,7 @@ from sat.tools.xml_tools import tupleList2dataForm from sat.tools.misc import TriggerManager from glob import glob +from uuid import uuid4 try: from twisted.words.protocols.xmlstream import XMPPHandler @@ -103,10 +104,8 @@ CONST[name] = value def __init__(self): - #TODO: standardize callback system - - self.__general_cb_map = {} # callback called for general reasons (key = name) - self.__private_data = {} # used for internal callbacks (key = id) + self._cb_map = {} # map from callback_id to callbacks + self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed self.profiles = {} self.plugins = {} self.menus = {} # dynamic menus. key: (type, category, name), value: menu data (dictionnary) @@ -157,7 +156,7 @@ self.bridge.register("updateContact", self.updateContact) self.bridge.register("delContact", self.delContact) self.bridge.register("isConnected", self.isConnected) - self.bridge.register("launchAction", self.launchAction) + self.bridge.register("launchAction", self.launchCallback) self.bridge.register("confirmationAnswer", self.confirmationAnswer) self.bridge.register("getProgress", self.getProgress) self.bridge.register("getMenus", self.getMenus) @@ -388,7 +387,8 @@ return next_id - def registerNewAccountCB(self, id, data, profile): + def registerNewAccountCB(self, data, profile): + # FIXME: to be removed/redone elsewhere user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] password = self.memory.getParamA("Password", "Connection", profile_key=profile) server = self.memory.getParamA("Server", "Connection", profile_key=profile) @@ -407,8 +407,6 @@ {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}}, self.regisConfirmCB, profile) print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============" - print "id=", id - print "data=", data def regisConfirmCB(self, id, accepted, data, profile): print _("register Confirmation CB ! (%s)") % str(accepted) @@ -428,6 +426,7 @@ @param fields: list of tuples (name, value) @return: tuple: (id, deferred) """ + # FIXME: to be removed profile = self.memory.getProfileName(profile_key) assert(profile) @@ -469,29 +468,6 @@ return False return self.profiles[profile].isConnected() - def launchAction(self, type, data, profile_key): - """Launch a specific action asked by client - @param type: action type (button) - @param data: needed data to launch the action - - @return: action id for result, or empty string in case or error - """ - profile = self.memory.getProfileName(profile_key) - if not profile: - error(_('trying to launch action with a non-existant profile')) - raise Exception # TODO: raise a proper exception - if type == "button": - try: - cb_name = data['callback_id'] - except KeyError: - error(_("Incomplete data")) - return "" - id = sat_next_id() - self.callGeneralCB(cb_name, id, data, profile=profile) - return id - else: - error(_("Unknown action type")) - return "" ## jabber methods ## @@ -808,6 +784,7 @@ @param data: data (depend of confirmation conf_type) @param cb: callback called with the answer """ + # FIXME: use XMLUI and *callback methods for dialog client = self.getClient(profile) if not client: raise exceptions.ProfileUnknownError(_("Asking confirmation a non-existant profile")) @@ -863,24 +840,53 @@ #debug("Requested progress for unknown progress_id") return data - def registerGeneralCB(self, name, CB): - """Register a callback called for general reason""" - self.__general_cb_map[name] = CB - - def removeGeneralCB(self, name): - """Remove a general callback""" - if name not in self.__general_cb_map: - error(_("Trying to remove an unknow general callback")) + def registerCallback(self, callback, *args, **kwargs): + """ Register a callback. + Use with_data=True in kwargs if the callback use the optional data dict + use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible + @param callback: any callable + @return: id of the registered callback + """ + callback_id = kwargs.pop('force_id', None) + if callback_id is None: + callback_id = str(uuid4()) else: - del self.__general_cb_map[name] + if callback_id in self._cb_map: + raise exceptions.ConflictError(_(u"id already registered")) + self._cb_map[callback_id] = (callback, args, kwargs) + return callback_id + + def removeCallback(self, callback_id): + """ Remove a previously registered callback + @param callback_id: id returned by [registerCallback] """ + del self._cb_map[callback_id] - def callGeneralCB(self, name, *args, **kwargs): - """Call general function back""" + def launchCallback(self, callback_id, data=None, profile_key="@NONE@"): + """Launch a specific callback + @param callback_id: id of the action (callback) to launch + @param data: optional data + @profile_key: %(doc_profile_key)s + @return: a deferred which fire a dict where key can be: + - xmlui: a XMLUI need to be displayed + """ + profile = self.memory.getProfileName(profile_key) + if not profile: + raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile')) + try: - return self.__general_cb_map[name](*args, **kwargs) + callback, args, kwargs = self._cb_map[callback_id] except KeyError: - error(_("Trying to call unknown function (%s)") % name) - return None + raise exceptions.DataError("Unknown callback id") + + if kwargs.get("with_data", False): + if data is None: + raise exceptions.DataError("Required data for this callback is missing") + args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args + args.insert(0, data) + kwargs["profile"] = profile + del kwargs["with_data"] + + return defer.maybeDeferred(callback, *args, **kwargs) #Menus management @@ -925,6 +931,7 @@ @param profile_key: %(doc_profile_key)s @return: XMLUI or empty string if it's a one shot menu """ + # TODO: menus should use launchCallback profile = self.memory.getProfileName(profile_key) if not profile: raise exceptions.ProfileUnknownError diff -r 86224a13cc1d -r 93bd868b8fb6 src/memory/memory.py --- a/src/memory/memory.py Tue Dec 24 15:19:08 2013 +0100 +++ b/src/memory/memory.py Tue Dec 24 15:19:08 2013 +0100 @@ -212,7 +212,7 @@ self.default_profile = None self.params = {} self.params_gen = {} - host.registerGeneralCB("registerNewAccount", host.registerNewAccountCB) + host.registerCallback(host.registerNewAccountCB, force_id="registerNewAccount") def createProfile(self, profile): """Create a new profile diff -r 86224a13cc1d -r 93bd868b8fb6 src/plugins/plugin_misc_quiz.py --- a/src/plugins/plugin_misc_quiz.py Tue Dec 24 15:19:08 2013 +0100 +++ b/src/plugins/plugin_misc_quiz.py Tue Dec 24 15:19:08 2013 +0100 @@ -26,8 +26,6 @@ import random from wokkel import data_form -from sat.tools.xml_tools import dataForm2XML -from sat_frontends.tools.games import TarotCard from time import time diff -r 86224a13cc1d -r 93bd868b8fb6 src/tools/xml_tools.py --- a/src/tools/xml_tools.py Tue Dec 24 15:19:08 2013 +0100 +++ b/src/tools/xml_tools.py Tue Dec 24 15:19:08 2013 +0100 @@ -219,7 +219,7 @@ class XMLUI(object): """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" - def __init__(self, panel_type, layout="vertical", title=None): + def __init__(self, panel_type, layout="vertical", title=None, submit_id=None): """Init SàT XML Panel @param panel_type: one of - window (new window) @@ -232,6 +232,7 @@ (usually one for a label, the next for the element) - tabs: elemens are in categories with tabs (notebook) @param title: title or default if None + @param submit_id: callback id to call for panel_type we can submit (form, param) """ if not panel_type in ['window', 'form', 'param']: error(_("Unknown panel type [%s]") % panel_type) @@ -244,6 +245,8 @@ top_element.setAttribute("type", panel_type) if title: top_element.setAttribute("title", title) + if submit_id: + top_element.setAttribute("submit", submit_id) self.parentTabsLayout = None # used only we have 'tabs' layout self.currentCategory = None # used only we have 'tabs' layout self.currentLayout = None @@ -369,7 +372,7 @@ @fields_back: list of names of field to give back when pushing the button """ elem = self._createElem('button', name, self.currentLayout) - elem.setAttribute('callback_id', callback_id) + elem.setAttribute('callback', callback_id) elem.setAttribute('value', value) for field in fields_back: fback_el = self.doc.createElement('field_back')