diff src/core/sat_main.py @ 773:eac23b1aad90

core: dynamics menus refactoring: - menu now use generic callback system, with extra data - asyncMenuCall is removed in favor of launchAction - menu_id (== callback_id) is used to identify menu instead of category/name/type tuple - i18n is managed throught deferred translation, and returned with _i18n suffix e.g.: menu (D_('File'), D_('Open')): (u'File', u'Open') is menu_path, (u'Fichier', u'Ouvrir') is french menu_path_i18n. - type actually can have the following values: - NORMAL: classical menu - JID_CONTEXT: contextual menu, used with any jid - ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster. - ROSTER_GROUP_CONTEXT: contextual menu, use with groups - security_limit is used, in the same way as for parameters - when using importMenu, callback can be an actual callback, or one already registered with registerCallback
author Goffi <goffi@goffi.org>
date Sun, 29 Dec 2013 17:10:14 +0100
parents bfabeedbf32e
children 5642939d254e
line wrap: on
line diff
--- a/src/core/sat_main.py	Sun Dec 29 17:10:10 2013 +0100
+++ b/src/core/sat_main.py	Sun Dec 29 17:10:14 2013 +0100
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from sat.core.i18n import _
+from sat.core.i18n import _, languageSwitch
 from twisted.application import service
 from twisted.internet import defer
 
@@ -39,7 +39,7 @@
 from sat.core.default_config import CONST
 from sat.core import xmpp
 from sat.core import exceptions
-from sat.memory.memory import Memory
+from sat.memory.memory import Memory, NO_SECURITY_LIMIT
 from sat.tools.xml_tools import tupleList2dataForm
 from sat.tools.misc import TriggerManager
 from glob import glob
@@ -106,10 +106,10 @@
 
     def __init__(self):
         self._cb_map = {}  # map from callback_id to callbacks
+        self._menus = {}  # dynamic menus. key: callback_id, value: menu data (dictionnary)
         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)
 
         self.memory = Memory(self)
 
@@ -162,7 +162,6 @@
         self.bridge.register("getProgress", self.getProgress)
         self.bridge.register("getMenus", self.getMenus)
         self.bridge.register("getMenuHelp", self.getMenuHelp)
-        self.bridge.register("asyncCallMenu", self.callMenu)
 
         self.memory.initialized.addCallback(self._postMemoryInit)
 
@@ -891,57 +890,85 @@
 
     #Menus management
 
-    def importMenu(self, category, name, callback, callback_args=None, callback_kwargs=None, help_string="", type_="NORMAL"):
+    def importMenu(self, path, callback, security_limit=NO_SECURITY_LIMIT, help_string="", type_="NORMAL"):
         """register a new menu for frontends
-        @param category: category of the menu
-        @param name: menu item entry
-        @param callback: method to be called when menuitem is selected
-        @param callback_args: optional arguments to forward to callback
-        @param callback_kwargs: optional keywords arguments to forward to callback
+        @param path: path to go to the menu (category/subcategory/.../item), must be an iterable (e.g.: ("File", "Open"))
+            /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
+        @param callback: method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback]
+        @param security_limit: %(doc_security_limit)s
+            /!\ security_limit MUST be added to data in launchCallback if used
+        @param help_string: string used to indicate what the menu do (can be show as a tooltip).
+            /!\ use D_() instead of _() for translations
+        @param type: one of:
+            - NORMAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open)
+            - JID_CONTEXT: contextual menu, used with any jid (e.g.: ad hoc commands, jid is already filled)
+            - ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster.
+            - ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled)
+        @return: menu_id (same as callback_id)
         """
-        # TODO: manage translations
-        if (type_, category, name) in self.menus:
-            raise exceptions.ConflictError("Menu already exists")
-        menu_data = {'callback': callback, 'help_string': help_string}
-        if callback_args is not None:
-            assert(isinstance(callback_args, list))
-            menu_data['callback_args'] = callback_args
-        if callback_kwargs is not None:
-            assert(isinstance(callback_kwargs, dict))
-            menu_data['callback_kwargs'] = callback_kwargs
-        self.menus[(type_, category, name)] = menu_data
+
+        if callable(callback):
+            callback_id = self.registerCallback(callback, with_data=True)
+        elif isinstance(callback, basestring):
+            # The callback is already registered
+            callback_id = callback
+            try:
+                callback, args, kwargs = self._cb_map[callback_id]
+            except KeyError:
+                raise exceptions.DataError("Unknown callback id")
+            kwargs["with_data"] = True # we have to be sure that we use extra data
+        else:
+            raise exceptions.DataError("Unknown callback type")
+
+        for menu_data in self._menus.itervalues():
+            if menu_data['path'] == path and menu_data['type'] == type_:
+                raise exceptions.ConflictError(_("A menu with the same path and type already exists"))
+
+        menu_data = {'path': path,
+                     'security_limit': security_limit,
+                     'help_string': help_string,
+                     'type': type_
+                    }
+
+        self._menus[callback_id] = menu_data
+
+        return callback_id
 
-    def getMenus(self):
-        """Return all menus registered"""
-        # TODO: manage translations
-        return self.menus.keys()
+    def getMenus(self, language='', security_limit = NO_SECURITY_LIMIT):
+        """Return all menus registered
+        @param language: language used for translation, or empty string for default
+        @param security_limit: %(doc_security_limit)s
+        @return: array of tuple with:
+            - menu id (same as callback_id)
+            - menu type
+            - raw menu path (array of strings)
+            - translated menu path
 
-    def getMenuHelp(self, category, name, type_="NORMAL"):
-        """return the help string of the menu"""
-        # TODO: manage translations
+        """
+        ret = []
+        for menu_id, menu_data in self._menus.iteritems():
+            type_ = menu_data['type']
+            path = menu_data['path']
+            languageSwitch(language)
+            path_i18n = [_(elt) for elt in path]
+            languageSwitch()
+            ret.append((menu_id, type_, path, path_i18n))
+
+        return ret
+
+    def getMenuHelp(self, menu_id, language=''):
+        """
+        return the help string of the menu
+        @param menu_id: id of the menu (same as callback_id)
+        @param language: language used for translation, or empty string for default
+        @param return: translated help
+
+        """
         try:
-            return self.menus[(type_, category, name)]['help_string']
+            menu_data = self._menus[menu_id]
         except KeyError:
             raise exceptions.DataError("Trying to access an unknown menu")
-
-    def callMenu(self, category, name, type_="NORMAL", profile_key='@NONE@'):
-        """ Call a dynamic menu
-        @param category: category of the menu to call
-        @param name: name of the menu to call
-        @param type_: type of the menu to call
-        @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
-        menu_data = self.menus[(type_, category, name)]
-        callback = menu_data['callback']
-        args = menu_data.get('callback_args', ())
-        kwargs = menu_data.get('callback_kwargs', {}).copy()
-        kwargs["profile"] = profile
-        try:
-            return defer.maybeDeferred(callback, *args, **kwargs)
-        except KeyError:
-            raise exceptions.DataError("Trying to access an unknown menu (%(type)s/%(category)s/%(name)s)" % {'type': type_, 'category': category, 'name': name})
+        languageSwitch(language)
+        help_string = _(menu_data['help_string'])
+        languageSwitch()
+        return help_string