Mercurial > libervia-backend
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}) |