comparison 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
comparison
equal deleted inserted replaced
772:dd07fc737d6c 773:eac23b1aad90
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _, languageSwitch
21 from twisted.application import service 21 from twisted.application import service
22 from twisted.internet import defer 22 from twisted.internet import defer
23 23
24 from twisted.words.protocols.jabber import jid, xmlstream 24 from twisted.words.protocols.jabber import jid, xmlstream
25 from twisted.words.xish import domish 25 from twisted.words.xish import domish
37 import os.path 37 import os.path
38 38
39 from sat.core.default_config import CONST 39 from sat.core.default_config import CONST
40 from sat.core import xmpp 40 from sat.core import xmpp
41 from sat.core import exceptions 41 from sat.core import exceptions
42 from sat.memory.memory import Memory 42 from sat.memory.memory import Memory, NO_SECURITY_LIMIT
43 from sat.tools.xml_tools import tupleList2dataForm 43 from sat.tools.xml_tools import tupleList2dataForm
44 from sat.tools.misc import TriggerManager 44 from sat.tools.misc import TriggerManager
45 from glob import glob 45 from glob import glob
46 from uuid import uuid4 46 from uuid import uuid4
47 47
104 raise Exception 104 raise Exception
105 CONST[name] = value 105 CONST[name] = value
106 106
107 def __init__(self): 107 def __init__(self):
108 self._cb_map = {} # map from callback_id to callbacks 108 self._cb_map = {} # map from callback_id to callbacks
109 self._menus = {} # dynamic menus. key: callback_id, value: menu data (dictionnary)
109 self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed 110 self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed
110 self.profiles = {} 111 self.profiles = {}
111 self.plugins = {} 112 self.plugins = {}
112 self.menus = {} # dynamic menus. key: (type, category, name), value: menu data (dictionnary)
113 113
114 self.memory = Memory(self) 114 self.memory = Memory(self)
115 115
116 local_dir = self.memory.getConfig('', 'local_dir') 116 local_dir = self.memory.getConfig('', 'local_dir')
117 if not os.path.exists(local_dir): 117 if not os.path.exists(local_dir):
160 self.bridge.register("launchAction", self.launchCallback) 160 self.bridge.register("launchAction", self.launchCallback)
161 self.bridge.register("confirmationAnswer", self.confirmationAnswer) 161 self.bridge.register("confirmationAnswer", self.confirmationAnswer)
162 self.bridge.register("getProgress", self.getProgress) 162 self.bridge.register("getProgress", self.getProgress)
163 self.bridge.register("getMenus", self.getMenus) 163 self.bridge.register("getMenus", self.getMenus)
164 self.bridge.register("getMenuHelp", self.getMenuHelp) 164 self.bridge.register("getMenuHelp", self.getMenuHelp)
165 self.bridge.register("asyncCallMenu", self.callMenu)
166 165
167 self.memory.initialized.addCallback(self._postMemoryInit) 166 self.memory.initialized.addCallback(self._postMemoryInit)
168 167
169 def _postMemoryInit(self, ignore): 168 def _postMemoryInit(self, ignore):
170 """Method called after memory initialization is done""" 169 """Method called after memory initialization is done"""
889 888
890 return defer.maybeDeferred(callback, *args, **kwargs) 889 return defer.maybeDeferred(callback, *args, **kwargs)
891 890
892 #Menus management 891 #Menus management
893 892
894 def importMenu(self, category, name, callback, callback_args=None, callback_kwargs=None, help_string="", type_="NORMAL"): 893 def importMenu(self, path, callback, security_limit=NO_SECURITY_LIMIT, help_string="", type_="NORMAL"):
895 """register a new menu for frontends 894 """register a new menu for frontends
896 @param category: category of the menu 895 @param path: path to go to the menu (category/subcategory/.../item), must be an iterable (e.g.: ("File", "Open"))
897 @param name: menu item entry 896 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
898 @param callback: method to be called when menuitem is selected 897 @param callback: method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback]
899 @param callback_args: optional arguments to forward to callback 898 @param security_limit: %(doc_security_limit)s
900 @param callback_kwargs: optional keywords arguments to forward to callback 899 /!\ security_limit MUST be added to data in launchCallback if used
901 """ 900 @param help_string: string used to indicate what the menu do (can be show as a tooltip).
902 # TODO: manage translations 901 /!\ use D_() instead of _() for translations
903 if (type_, category, name) in self.menus: 902 @param type: one of:
904 raise exceptions.ConflictError("Menu already exists") 903 - NORMAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open)
905 menu_data = {'callback': callback, 'help_string': help_string} 904 - JID_CONTEXT: contextual menu, used with any jid (e.g.: ad hoc commands, jid is already filled)
906 if callback_args is not None: 905 - ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster.
907 assert(isinstance(callback_args, list)) 906 - ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled)
908 menu_data['callback_args'] = callback_args 907 @return: menu_id (same as callback_id)
909 if callback_kwargs is not None: 908 """
910 assert(isinstance(callback_kwargs, dict)) 909
911 menu_data['callback_kwargs'] = callback_kwargs 910 if callable(callback):
912 self.menus[(type_, category, name)] = menu_data 911 callback_id = self.registerCallback(callback, with_data=True)
913 912 elif isinstance(callback, basestring):
914 def getMenus(self): 913 # The callback is already registered
915 """Return all menus registered""" 914 callback_id = callback
916 # TODO: manage translations 915 try:
917 return self.menus.keys() 916 callback, args, kwargs = self._cb_map[callback_id]
918 917 except KeyError:
919 def getMenuHelp(self, category, name, type_="NORMAL"): 918 raise exceptions.DataError("Unknown callback id")
920 """return the help string of the menu""" 919 kwargs["with_data"] = True # we have to be sure that we use extra data
921 # TODO: manage translations 920 else:
921 raise exceptions.DataError("Unknown callback type")
922
923 for menu_data in self._menus.itervalues():
924 if menu_data['path'] == path and menu_data['type'] == type_:
925 raise exceptions.ConflictError(_("A menu with the same path and type already exists"))
926
927 menu_data = {'path': path,
928 'security_limit': security_limit,
929 'help_string': help_string,
930 'type': type_
931 }
932
933 self._menus[callback_id] = menu_data
934
935 return callback_id
936
937 def getMenus(self, language='', security_limit = NO_SECURITY_LIMIT):
938 """Return all menus registered
939 @param language: language used for translation, or empty string for default
940 @param security_limit: %(doc_security_limit)s
941 @return: array of tuple with:
942 - menu id (same as callback_id)
943 - menu type
944 - raw menu path (array of strings)
945 - translated menu path
946
947 """
948 ret = []
949 for menu_id, menu_data in self._menus.iteritems():
950 type_ = menu_data['type']
951 path = menu_data['path']
952 languageSwitch(language)
953 path_i18n = [_(elt) for elt in path]
954 languageSwitch()
955 ret.append((menu_id, type_, path, path_i18n))
956
957 return ret
958
959 def getMenuHelp(self, menu_id, language=''):
960 """
961 return the help string of the menu
962 @param menu_id: id of the menu (same as callback_id)
963 @param language: language used for translation, or empty string for default
964 @param return: translated help
965
966 """
922 try: 967 try:
923 return self.menus[(type_, category, name)]['help_string'] 968 menu_data = self._menus[menu_id]
924 except KeyError: 969 except KeyError:
925 raise exceptions.DataError("Trying to access an unknown menu") 970 raise exceptions.DataError("Trying to access an unknown menu")
926 971 languageSwitch(language)
927 def callMenu(self, category, name, type_="NORMAL", profile_key='@NONE@'): 972 help_string = _(menu_data['help_string'])
928 """ Call a dynamic menu 973 languageSwitch()
929 @param category: category of the menu to call 974 return help_string
930 @param name: name of the menu to call
931 @param type_: type of the menu to call
932 @param profile_key: %(doc_profile_key)s
933 @return: XMLUI or empty string if it's a one shot menu
934 """
935 # TODO: menus should use launchCallback
936 profile = self.memory.getProfileName(profile_key)
937 if not profile:
938 raise exceptions.ProfileUnknownError
939 menu_data = self.menus[(type_, category, name)]
940 callback = menu_data['callback']
941 args = menu_data.get('callback_args', ())
942 kwargs = menu_data.get('callback_kwargs', {}).copy()
943 kwargs["profile"] = profile
944 try:
945 return defer.maybeDeferred(callback, *args, **kwargs)
946 except KeyError:
947 raise exceptions.DataError("Trying to access an unknown menu (%(type)s/%(category)s/%(name)s)" % {'type': type_, 'category': category, 'name': name})