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
This commit is contained in:
Goffi 2013-12-29 17:10:14 +01:00
commit b7bf434cd2

View file

@ -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 @@ import os.path
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
#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
def getMenus(self):
"""Return all menus registered"""
# TODO: manage translations
return self.menus.keys()
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")
def getMenuHelp(self, category, name, type_="NORMAL"):
"""return the help string of the menu"""
# TODO: manage translations
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, 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
"""
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