# HG changeset patch # User Goffi # Date 1426621322 -3600 # Node ID 849ffb24d5bf550d927360fb4f751a05e178ca32 # Parent 941e53b3af5cf8266e915c651d22462ed7ee1d2c browser side: menus refactorisation: - use of the new quick_frontends.quick_menus module, resulting in a big code simplification in Libervia - menu are added in there respective modules: main menus are done directely in libervia_main, while tarot and radiocol menus are done in game_tarot and game_radiocol - launchAction has the same signature as in QuickApp - base_menu: there are now 2 classes to launch an action: MenuCmd which manage quick_menus classes, and SimpleCmd to launch a generic callback - base_menu: MenuNode has been removed as logic is now in quick_menus - base_menu: GenericMenuBar.update method can be called to fully (re)build the menus - base_widget: removed WidgetSubMenuBar which is no more useful (GenericMenuBar do the same thing) - plugin_menu_context is used in LiberviaWidget and other classes with menus to indicate which menu types must be used - otr menus hooks are temporarily removed, will be fixed soon diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/libervia_main.py --- a/src/browser/libervia_main.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/libervia_main.py Tue Mar 17 20:42:02 2015 +0100 @@ -25,8 +25,11 @@ log = getLogger(__name__) ### +from sat.core.i18n import D_ + from sat_frontends.quick_frontend.quick_app import QuickApp from sat_frontends.quick_frontend import quick_widgets +from sat_frontends.quick_frontend import quick_menus from sat_frontends.tools.misc import InputHistory from sat_frontends.tools import strings @@ -73,7 +76,7 @@ # the existing widget will be eventually removed from its parent # and added to new libervia_widget.WidgetsPanel, or replaced to the expected # position if the previous and the new parent are the same. -REUSE_EXISTING_LIBERVIA_WIDGETS = True +# REUSE_EXISTING_LIBERVIA_WIDGETS = True # FIXME class SatWebFrontend(InputHistory, QuickApp): @@ -250,16 +253,46 @@ def displayNotification(self, title, body): self.notification.notify(title, body) - def gotMenus(self, menus): + def gotMenus(self, backend_menus): """Put the menus data in cache and build the main menu bar - @param menus (list[tuple]): menu data + @param backend_menus (list[tuple]): menu data from backend """ - self.callListeners('gotMenus', menus) # FIXME: to be done another way or moved to quick_app - self.menus = {} - for id_, type_, path, path_i18n in menus: - self.menus.setdefault(type_, []).append((id_, path, path_i18n)) - self.panel.menu.createMenus() + main_menu = self.panel.menu # most of global menu callbacks are in main_menu + + # Categories (with icons) + self.menus.addCategory(C.MENU_GLOBAL, [D_(u"General")], extra={'icon': 'home'}) + self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Contacts")], extra={'icon': 'social'}) + self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Groups")], extra={'icon': 'social'}) + self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Games")], extra={'icon': 'games'}) + + # menus to have before backend menus + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Groups"), D_(u"Discussion")), callback=main_menu.onJoinRoom) + + # menus added by the backend/plugins (include other types than C.MENU_GLOBAL) + self.menus.addMenus(backend_menus, top_extra={'icon': 'plugins'}) + + # menus to have under backend menus + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Contacts"), D_(u"Manage groups")), callback=main_menu.onManageContactGroups) + + # separator and right hand menus + self.menus.addMenuItem(C.MENU_GLOBAL, [], quick_menus.MenuSeparator()) + + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Help"), D_("Social contract")), top_extra={'icon': 'help'}, callback=main_menu.onSocialContract) + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Help"), D_("About")), callback=main_menu.onAbout) + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Account")), top_extra={'icon': 'settings'}, callback=main_menu.onAccount) + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Parameters")), callback=main_menu.onParameters) + # XXX: temporary, will change when a full profile will be managed in SàT + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Upload avatar")), callback=main_menu.onAvatarUpload) + + # we call listener to have menu added by local classes/plugins + self.callListeners('gotMenus') # FIXME: to be done another way or moved to quick_app + + # and finally the menus which must appear at the bottom + self.menus.addMenu(C.MENU_GLOBAL, (D_(u"General"), D_(u"Disconnect")), callback=main_menu.onDisconnect) + + # we can now display all the menus + main_menu.update(C.MENU_GLOBAL) def _isRegisteredCB(self, result): registered, warning = result @@ -412,7 +445,7 @@ dialog.InfoDialog("Error", unicode(err_obj), Width="400px").center() - def launchAction(self, callback_id, data): + def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE): """ Launch a dynamic action @param callback_id: id of the action to launch @param data: data needed only for certain actions @@ -420,7 +453,7 @@ """ if data is None: data = {} - self.bridge.launchAction(callback_id, data, profile=C.PROF_KEY_NONE, callback=self._actionCb, errback=self._actionEb) + self.bridge.launchAction(callback_id, data, profile=profile, callback=self._actionCb, errback=self._actionEb) def _getContactsCB(self, contacts_data): for contact_ in contacts_data: diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/base_menu.py --- a/src/browser/sat_browser/base_menu.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/base_menu.py Tue Mar 17 20:42:02 2015 +0100 @@ -27,13 +27,11 @@ from sat.core.log import getLogger log = getLogger(__name__) -from sat.core import exceptions from pyjamas.ui.MenuBar import MenuBar -from pyjamas.ui.UIObject import UIObject from pyjamas.ui.MenuItem import MenuItem from pyjamas import Window - -import re +from sat_frontends.quick_frontend import quick_menus +from sat_browser import html_tools unicode = str # FIXME: pyjamas workaround @@ -42,186 +40,29 @@ class MenuCmd(object): """Return an object with an "execute" method that can be set to a menu item callback""" - def __init__(self, object_, handler=None, data=None): - """ - @param object_ (object): a callable or a class instance - @param handler (unicode): method name if object_ is a class instance - @param data (dict): data to pass as the callback argument + def __init__(self, menu_item, caller=None): """ - if handler is None: - assert(callable(object_)) - self.callback = object_ - else: - self.callback = getattr(object_, handler) - self.data = data + @param menu_item(quick_menu.MenuItem): instance of a callbable MenuItem + @param caller: menu caller + """ + self.item = menu_item + self._caller = caller def execute(self): - log.debug("execute %s" % self.callback) - self.callback(self.data) if self.data else self.callback() - - -class PluginMenuCmd(object): - """Like MenuCmd, but instead of executing a method, it will command the bridge to launch an action""" - - def __init__(self, host, action_id, menu_data=None): - self.host = host - self.action_id = action_id - self.menu_data = menu_data - - def execute(self): - self.host.launchAction(self.action_id, self.menu_data) + self.item.call(self._caller) -class MenuNode(object): - """MenuNode is a basic data structure to build a menu hierarchy. - When Pyjamas MenuBar and MenuItem defines UI elements, MenuNode - stores the logical relation between them.""" - - """This class has been introduced to deal with "flattened menus", when you - want the items of a sub-menu to be displayed in the parent menu. It was - needed to break the naive relation of "one MenuBar = one category".""" - - def __init__(self, name=None, item=None, menu=None, flat_level=0): - """ - @param name (unicode): node name - @param item (MenuItem): associated menu item - @param menu (GenericMenuBar): associated menu bar - @param flat_level (int): sub-menus until that level see their items - displayed in the parent menu bar, instead of in a callback popup. - """ - self.name = name - self.item = item or None # associated menu item - self.menu = menu or None # associated menu bar (sub-menu) - self.flat_level = max(flat_level, -1) - self.children = [] - - def _getOrCreateCategory(self, path, path_i18n=None, types=None, create=False, sub_menu=None): - """Return the requested category. If create is True, path_i18n and - types are specified, recursively create the category and its parent. - - @param path (list[unicode]): path to the category - @param path_i18n (list[unicode]): internationalized path to the category - @param types (list[unicode]): types of the category and its parents - @param create (bool): if True, create the category - @param sub_menu (GenericMenuBar): instance to popup as the category - sub-menu, if it is created. Otherwise keep the previous sub-menu. - @return: MenuNode or None - """ - assert(len(path) > 0 and len(path) == len(path_i18n) == len(types)) - if len(path) > 1: - cat = self._getOrCreateCategory(path[:1], path_i18n[:1], types[:1], create) - return cat._getOrCreateCategory(path[1:], path_i18n[1:], types[1:], create, sub_menu) if cat else None - cats = [child for child in self.children if child.menu and child.name == path[0]] - if len(cats) == 1: - return cats[0] - assert(cats == []) # there should not be more than 1 category with the same name - if create: - html = self.menu.getCategoryHTML(path_i18n[0], types[0]) - sub_menu = sub_menu if sub_menu else GenericMenuBar(self.menu.host, vertical=True) - return self.addItem(html, True, sub_menu, name=path[0]) - return None - - def getCategories(self, target_path=None): - """Return all the categories of the current node, or those of the - sub-category which is specified by target_path. - - @param target_path (list[unicode]): path to the target node - @return: list[MenuNode] - """ - assert(self.menu) # this method applies to category nodes - if target_path: - assert(isinstance(target_path, list)) - cat = self._getOrCreateCategory(target_path[:-1]) - return cat.getCategories(target_path[-1:]) if cat else None - return [child for child in self.children if child.menu] - - def addMenuItem(self, path, path_i18n, types, callback=None, asHTML=False): - """Recursively add a new node, which could be a category or a leaf node. - - @param path (list[unicode], unicode): path to the item - @param path_i18n (list[unicode], unicode): internationalized path to the item - @param types (list[unicode], unicode): types of the item and its parents - @param callback (MenuCmd, PluginMenuCmd or GenericMenuBar): instance to - execute as a leaf's callback or to popup as a category sub-menu - @param asHTML (boolean): True to display the UI item as HTML - """ - log.info("addMenuItem: %s %s %s %s" % (path, path_i18n, types, callback)) +class SimpleCmd(object): + """Return an object with an "executre" method that launch a callback""" - leaf_node = hasattr(callback, "execute") - category = isinstance(callback, GenericMenuBar) - assert(not leaf_node or not category) - - path = [path] if isinstance(path, unicode) else path - path_i18n = [path_i18n] if isinstance(path_i18n, unicode) else path_i18n - types = [types for dummy in range(len(path_i18n))] if isinstance(types, unicode) else types - - if category: - return self._getOrCreateCategory(path, path_i18n, types, True, callback) - - if len(path) == len(path_i18n) - 1: - path.append(None) # dummy name for a leaf node - - parent = self._getOrCreateCategory(path[:-1], path_i18n[:-1], types[:-1], True) - return parent.addItem(path_i18n[-1], asHTML=asHTML, popup=callback) - - def addCategory(self, path, path_i18n, types, menu_bar=None): - """Recursively add a new category. - - @param path (list[unicode], unicode): path to the category - @param path_i18n (list[unicode], unicode): internationalized path to the category - @param types (list[unicode], unicode): types of the category and its parents - @param menu_bar (GenericMenuBar): instance to popup as the category sub-menu. + def __init__(self, callback): """ - if menu_bar: - assert(isinstance(menu_bar, GenericMenuBar)) - else: - menu_bar = GenericMenuBar(self.menu.host, vertical=True) - return self.addMenuItem(path, path_i18n, types, menu_bar) - - def addItem(self, item, asHTML=None, popup=None, name=None): - """Add a single child to the current node. + @param callback: method to call when menu is selected + """ + self.callback = callback - @param item: see MenuBar.addItem - @param asHTML: see MenuBar.addItem - @param popup: see MenuBar.addItem - @param name (unicode): the item node's name - """ - if item is None: # empty string is allowed to set a separator - return None - item = MenuBar.addItem(self.menu, item, asHTML, popup) - node_menu = item.getSubMenu() # node eventually uses it's own menu - - # XXX: all the dealing with flattened menus is done here - if self.flat_level > 0: - item.setSubMenu(None) # eventually remove any sub-menu callback - if item.getCommand(): - node_menu = None # node isn't a category, it needs no menu - else: - node_menu = self.menu # node uses the menu of its parent - item.setStyleName(self.menu.styles["flattened-category"]) - - node = MenuNode(name=name, item=item, menu=node_menu, flat_level=self.flat_level - 1) - self.children.append(node) - return node - - def addCachedMenus(self, type_, menu_data=None): - """Add cached menus to instance. - - @param type_: menu type like in sat.core.sat_main.importMenu - @param menu_data: data to send with these menus - """ - menus = self.menu.host.menus.get(type_, []) - for action_id, path, path_i18n in menus: - if len(path) != len(path_i18n): - log.error("inconsistency between menu paths") - continue - if isinstance(action_id, unicode): - callback = PluginMenuCmd(self.menu.host, action_id, menu_data) - elif callable(action_id): - callback = MenuCmd(action_id, data=menu_data) - else: - raise exceptions.InternalError - self.addMenuItem(path, path_i18n, 'plugins', callback) + def execute(self): + self.callback() class GenericMenuBar(MenuBar): @@ -239,45 +80,63 @@ """ MenuBar.__init__(self, vertical, **kwargs) self.host = host - self.styles = {'separator': 'menuSeparator', 'flattened-category': 'menuFlattenedCategory'} + self.styles = {} if styles: self.styles.update(styles) - if 'menu_bar' in self.styles: + try: self.setStyleName(self.styles['menu_bar']) - self.node = MenuNode(menu=self, flat_level=flat_level) + except KeyError: + pass + self.menus_container = None + self.flat_level = flat_level + + def update(self, type_, caller=None): + """Method to call when menus have changed + + @param type_: menu type like in sat.core.sat_main.importMenu + @param caller: instance linked to the menus + """ + self.menus_container = self.host.menus.getMainContainer(type_) + self._caller=caller + self.createMenus() @classmethod - def getCategoryHTML(cls, menu_name_i18n, type_): + def getCategoryHTML(cls, category): """Build the html to be used for displaying a category item. Inheriting classes may overwrite this method. - @param menu_name_i18n (unicode): internationalized category name - @param type_ (unicode): category type - @return: unicode + @param category(quick_menus.MenuCategory): category to add + @return(unicode): HTML to display """ - return menu_name_i18n + return html_tools.html_sanitize(category.name) + + def _buildMenus(self, container, flat_level, caller=None): + """Recursively build menus of the container - def setStyleName(self, style): - # XXX: pyjamas set the style to object string representation! - # FIXME: fix the bug upstream - menu_style = ['gwt-MenuBar'] - menu_style.append(menu_style[0] + '-' + ('vertical' if self.vertical else 'horizontal')) - for classname in style.split(' '): - if classname not in menu_style: - menu_style.append(classname) - UIObject.setStyleName(self, ' '.join(menu_style)) + @param container: a quick_menus.MenuContainer instance + @param caller: instance linked to the menus + """ + for child in container.getActiveMenus(): + if isinstance(child, quick_menus.MenuContainer): + item = self.addCategory(child, flat=bool(flat_level)) + submenu = item.getSubMenu() + if submenu is None: + submenu = self + submenu._buildMenus(child, flat_level-1 if flat_level else 0, caller) + elif isinstance(child, quick_menus.MenuSeparator): + item = MenuItem(text='', asHTML=None, StyleName="menuSeparator") + self.addItem(item) + elif isinstance(child, quick_menus.MenuItem): + self.addItem(child.name, False, MenuCmd(child, caller) if child.CALLABLE else None) + else: + log.error(u"Unknown child type: {}".format(child)) - def addStyleName(self, style): - # XXX: same kind of problem then with setStyleName - # FIXME: fix the bug upstream - if not re.search('(^| )%s( |$)' % style, self.getStyleName()): - UIObject.setStyleName(self, self.getStyleName() + ' ' + style) - - def removeStyleName(self, style): - # XXX: same kind of problem then with setStyleName - # FIXME: fix the bug upstream - style = re.sub('(^| )%s( |$)' % style, ' ', self.getStyleName()).strip() - UIObject.setStyleName(self, style) + def createMenus(self): + self.clearItems() + if self.menus_container is None: + log.debug("Menu is empty") + return + self._buildMenus(self.menus_container, self.flat_level, self._caller) def doItemAction(self, item, fireCommand): """Overwrites the default behavior for the popup menu to fit in the screen""" @@ -297,29 +156,28 @@ if item.getAbsoluteLeft() > max_left: self.popup.setPopupPosition(new_left, top) # eventually smooth the popup edges to fit the menu own style - if 'moved_popup' in self.styles: + try: self.popup.addStyleName(self.styles['moved_popup']) - - def getCategories(self, parent_path=None): - """Return all the categories items. + except KeyError: + pass - @return: list[CategoryItem] + def addCategory(self, category, menu_bar=None, flat=False): + """Add a new category. + + @param menu_container(quick_menus.MenuCategory): Category to add + @param menu_bar (GenericMenuBar): instance to popup as the category sub-menu. """ - return [cat.item for cat in self.node.getCategories(parent_path)] - - def addMenuItem(self, path, path_i18n, types, menu_cmd, asHTML=False): - return self.node.addMenuItem(path, path_i18n, types, menu_cmd, asHTML).item + html = self.getCategoryHTML(category) - def addCategory(self, path, path_i18n, types, menu_bar=None): - return self.node.addCategory(path, path_i18n, types, menu_bar).item - - def addItem(self, item, asHTML=None, popup=None): - return self.node.addItem(item, asHTML, popup).item + if menu_bar is not None: + assert not flat # can't have a menu_bar and be flat at the same time + sub_menu = menu_bar + elif not flat: + sub_menu = GenericMenuBar(self.host, vertical=True) + else: + sub_menu = None - def addCachedMenus(self, type_, menu_data=None): - self.node.addCachedMenus(type_, menu_data) - - def addSeparator(self): - """Add a separator between the categories""" - item = MenuItem(text='', asHTML=None, StyleName=self.styles['separator']) - return self.addItem(item) + item = self.addItem(html, True, sub_menu) + if flat: + item.setStyleName("menuFlattenedCategory") + return item diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/base_widget.py --- a/src/browser/sat_browser/base_widget.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/base_widget.py Tue Mar 17 20:42:02 2015 +0100 @@ -17,10 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import pyjd # this is dummy in pyjs from sat.core.log import getLogger log = getLogger(__name__) import base_menu +from sat_frontends.quick_frontend import quick_menus ### Exceptions ### @@ -53,30 +53,14 @@ base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, styles=menu_styles) # regroup all the dynamic menu categories in a sub-menu - sub_menu = WidgetSubMenuBar(host, vertical=True) - try: - parent.addMenus(sub_menu) - except (AttributeError, TypeError): # FIXME: pyjamas can throw a TypeError depending on compilation options - pass - else: - if len(sub_menu.getCategories()) > 0: - self.addCategory('', '', 'plugins', sub_menu) + for menu_context in parent.plugin_menu_context: + main_cont = host.menus.getMainContainer(menu_context) + if len(main_cont)>0: # we don't add the icon if the menu is empty + sub_menu = base_menu.GenericMenuBar(host, vertical=True, flat_level=1) + sub_menu.update(menu_context, parent) + menu_category = quick_menus.MenuCategory("plugins", extra={'icon':'plugins'}) + self.addCategory(menu_category, sub_menu) @classmethod - def getCategoryHTML(cls, menu_name_i18n, type_): - return cls.ITEM_TPL % type_ - - -class WidgetSubMenuBar(base_menu.GenericMenuBar): - - def __init__(self, host, vertical=True): - """ - - @param host (SatWebFrontend) - @param vertical (bool): if True, set the menu vertically - """ - base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, flat_level=1) - - @classmethod - def getCategoryHTML(cls, menu_name_i18n, type_): - return menu_name_i18n + def getCategoryHTML(cls, category): + return cls.ITEM_TPL % category.icon diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/chat.py --- a/src/browser/sat_browser/chat.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/chat.py Tue Mar 17 20:42:02 2015 +0100 @@ -20,10 +20,10 @@ from sat.core.log import getLogger log = getLogger(__name__) -from sat_frontends.tools.games import SYMBOLS +# from sat_frontends.tools.games import SYMBOLS from sat_frontends.tools import strings from sat_frontends.tools import jid -from sat_frontends.quick_frontend import quick_widgets, quick_games +from sat_frontends.quick_frontend import quick_widgets, quick_games, quick_menus from sat_frontends.quick_frontend.quick_chat import QuickChat from sat.core.i18n import _ @@ -134,6 +134,10 @@ assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE return list(self.profiles)[0] + @property + def plugin_menu_context(self): + return (C.MENU_ROOM,) if self.type == C.CHAT_GROUP else (C.MENU_SINGLE,) + # @classmethod # def createPanel(cls, host, item, type_=C.CHAT_ONE2ONE): # assert(item) @@ -167,16 +171,6 @@ else: self.host.showWarning(*self.getWarningData()) - def addMenus(self, menu_bar): - """Add cached menus to the header. - - @param menu_bar (GenericMenuBar): menu bar of the widget's header - """ - if self.type == C.CHAT_GROUP: - menu_bar.addCachedMenus(C.MENU_ROOM, {'room_jid': self.target.bare}) - elif self.type == C.CHAT_ONE2ONE: - menu_bar.addCachedMenus(C.MENU_SINGLE, {'jid': self.target}) - def getWarningData(self): if self.type not in [C.CHAT_ONE2ONE, C.CHAT_GROUP]: raise Exception("Unmanaged type !") @@ -349,3 +343,5 @@ quick_widgets.register(quick_games.Tarot, game_tarot.TarotPanel) quick_widgets.register(quick_games.Radiocol, game_radiocol.RadioColPanel) libervia_widget.LiberviaWidget.addDropKey("CONTACT", lambda host, item: host.displayWidget(Chat, jid.JID(item), dropped=True)) +quick_menus.QuickMenusManager.addDataCollector(C.MENU_ROOM, {'room_jid': 'target'}) +quick_menus.QuickMenusManager.addDataCollector(C.MENU_SINGLE, {'jid': 'target'}) diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/contact_panel.py --- a/src/browser/sat_browser/contact_panel.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/contact_panel.py Tue Mar 17 20:42:02 2015 +0100 @@ -145,8 +145,8 @@ except KeyError: box = contact_widget.ContactBox(self.host, contact_jid, style_name=self.contacts_style, - menu_types=self.contacts_menus, - display=self.contacts_display) + display=self.contacts_display, + plugin_menu_context=self.contacts_menus) self._contacts[self._key(contact_jid)] = box return box diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/contact_widget.py --- a/src/browser/sat_browser/contact_widget.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/contact_widget.py Tue Mar 17 20:42:02 2015 +0100 @@ -22,6 +22,7 @@ log = getLogger(__name__) from sat.core import exceptions +from sat_frontends.quick_frontend import quick_menus from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HTML import HTML from pyjamas.ui.Image import Image @@ -114,24 +115,23 @@ class ContactBox(VerticalPanel, ClickHandler, libervia_widget.DragLabel): - def __init__(self, host, jid_, style_name=None, menu_types=None, display=C.CONTACT_DEFAULT_DISPLAY): + def __init__(self, host, jid_, style_name=None, display=C.CONTACT_DEFAULT_DISPLAY, plugin_menu_context=None): """ @param host (SatWebFrontend): host instance @param jid_ (jid.JID): contact JID @param style_name (unicode): CSS style name - @param menu_types (tuple): define the menu types that fit this - contact panel, with values from the menus type constants. @param contacts_display (tuple): prioritize the display methods of the contact's label with values in ("jid", "nick", "bare", "resource"). + @param plugin_menu_context (iterable): contexts of menus to have (list of C.MENU_* constant) """ + self.plugin_menu_context = [] if plugin_menu_context is None else plugin_menu_context VerticalPanel.__init__(self, StyleName=style_name or 'contactBox', VerticalAlignment='middle') ClickHandler.__init__(self) libervia_widget.DragLabel.__init__(self, jid_, "CONTACT", host) self.jid = jid_ self.label = ContactLabel(host, self.jid, display=display) - self.menu_types = menu_types - self.avatar = ContactMenuBar(self, host) if menu_types else Image() + self.avatar = ContactMenuBar(self, host) if plugin_menu_context else Image() try: # FIXME: dirty hack to force using an Image when the menu is actually empty self.avatar.items[0] except IndexError: @@ -141,10 +141,6 @@ self.add(self.label) self.addClickListener(self) - def addMenus(self, menu_bar): - for menu_type in self.menu_types: - menu_bar.addCachedMenus(menu_type, {'jid': unicode(self.jid.bare)}) - def setAlert(self, alert): """Show a visual indicator @@ -172,3 +168,5 @@ pass else: self.setAlert(False) + +quick_menus.QuickMenusManager.addDataCollector(C.MENU_JID_CONTEXT, lambda caller, dummy: {'jid': unicode(caller.jid.bare)}) diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/game_radiocol.py --- a/src/browser/sat_browser/game_radiocol.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/game_radiocol.py Tue Mar 17 20:42:02 2015 +0100 @@ -21,8 +21,9 @@ from sat.core.log import getLogger log = getLogger(__name__) -from sat.core.i18n import _ +from sat.core.i18n import _, D_ from sat_frontends.tools.misc import DEFAULT_MUC +from sat_frontends.tools import host_listener from constants import Const as C from pyjamas.ui.VerticalPanel import VerticalPanel @@ -40,6 +41,7 @@ import html_tools import file_tools +import dialog class MetadataPanel(FlexTable): @@ -320,3 +322,22 @@ def radiocolSongRejectedHandler(self, reason): Window.alert("Song rejected: %s" % reason) + + +## Menu + +def hostReady(host): + def onCollectiveRadio(self): + def callback(room_jid, contacts): + contacts = [unicode(contact) for contact in contacts] + room_jid_s = unicode(room_jid) if room_jid else '' + host.bridge.RadioCollective(contacts, room_jid_s, profile=C.PROF_KEY_NONE) + dialog.RoomAndContactsChooser(host, callback, ok_button="Choose", title="Collective Radio", visible=(False, True)) + + + def gotMenus(): + host.menus.addMenu(C.MENU_GLOBAL, (D_(u"Groups"), D_(u"Collective radio")), callback=onCollectiveRadio) + + host.addListener('gotMenus', gotMenus) + +host_listener.addListener(hostReady) diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/game_tarot.py --- a/src/browser/sat_browser/game_tarot.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/game_tarot.py Tue Mar 17 20:42:02 2015 +0100 @@ -21,8 +21,9 @@ from sat.core.log import getLogger log = getLogger(__name__) -from sat.core.i18n import _ +from sat.core.i18n import _, D_ from sat_frontends.tools.games import TarotCard +from sat_frontends.tools import host_listener from pyjamas.ui.AbsolutePanel import AbsolutePanel from pyjamas.ui.DockPanel import DockPanel @@ -34,6 +35,7 @@ from pyjamas.ui import HasAlignment from pyjamas import Window from pyjamas import DOM +from constants import Const as C import dialog import xmlui @@ -386,3 +388,21 @@ _dialog = dialog.GenericDialog(title, body, options=['NO_CLOSE']) body.setCloseCb(_dialog.close) _dialog.show() + + +## Menu + +def hostReady(host): + def onTarotGame(): + def onPlayersSelected(room_jid, other_players): + other_players = [unicode(contact) for contact in other_players] + room_jid_s = unicode(room_jid) if room_jid else '' + host.bridge.launchTarotGame(other_players, room_jid_s, profile=C.PROF_KEY_NONE) + dialog.RoomAndContactsChooser(host, onPlayersSelected, 3, title="Tarot", title_invite=_(u"Please select 3 other players"), visible=(False, True)) + + + def gotMenus(): + host.menus.addMenu(C.MENU_GLOBAL, (D_(u"Games"), D_(u"Tarot")), callback=onTarotGame) + host.addListener('gotMenus', gotMenus) + +host_listener.addListener(hostReady) diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/json.py --- a/src/browser/sat_browser/json.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/json.py Tue Mar 17 20:42:02 2015 +0100 @@ -50,7 +50,7 @@ # as profile is linked to browser session and managed server side, we remove them profile_removed = False try: - kwargs['profile'] # FIXME: workaround for pyjamas bug: KeyError is not raised iwith del + kwargs['profile'] # FIXME: workaround for pyjamas bug: KeyError is not raised with del del kwargs['profile'] profile_removed = True except KeyError: diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/libervia_widget.py --- a/src/browser/sat_browser/libervia_widget.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/libervia_widget.py Tue Mar 17 20:42:02 2015 +0100 @@ -346,8 +346,8 @@ button_group_wrapper = SimplePanel() button_group_wrapper.setStyleName('widgetHeader_buttonsWrapper') button_group = base_widget.WidgetMenuBar(parent, host) - button_group.addItem('', True, base_menu.MenuCmd(parent, 'onSetting')) - button_group.addItem('', True, base_menu.MenuCmd(parent, 'onClose')) + button_group.addItem('', True, base_menu.SimpleCmd(parent.onSetting)) + button_group.addItem('', True, base_menu.SimpleCmd(parent.onClose)) button_group_wrapper.add(button_group) self.add(button_group_wrapper) self.addStyleName('widgetHeader') @@ -357,18 +357,20 @@ class LiberviaWidget(DropCell, VerticalPanel, ClickHandler): """Libervia's widget which can replace itself with a dropped widget on DnD""" - def __init__(self, host, title='', info=None, selectable=False): + def __init__(self, host, title='', info=None, selectable=False, plugin_menu_context=None): """Init the widget @param host (SatWebFrontend): SatWebFrontend instance @param title (unicode): title shown in the header of the widget @param info (unicode): info shown in the header of the widget @param selectable (bool): True is widget can be selected by user + @param plugin_menu_context (iterable): contexts of menus to have (list of C.MENU_* constant) """ VerticalPanel.__init__(self) DropCell.__init__(self, host) ClickHandler.__init__(self) self._selectable = selectable + self._plugin_menu_context = [] if plugin_menu_context is None else plugin_menu_context self._title_id = HTMLPanel.createUniqueId() self._setting_button_id = HTMLPanel.createUniqueId() self._close_button_id = HTMLPanel.createUniqueId() @@ -395,6 +397,10 @@ # self.addCloseListener(onClose) # self.host.registerWidget(self) # FIXME + @property + def plugin_menu_context(self): + return self._plugin_menu_context + def getDebugName(self): return "%s (%s)" % (self, self._title.getText()) @@ -572,14 +578,6 @@ # the event will not propagate to children VerticalPanel.doAttachChildren(self) - def addMenus(self, menu_bar): - """Add menus to the header. - - This method can be overwritten by child classes. - @param menu_bar (GenericMenuBar): menu bar of the widget's header - """ - pass - # XXX: WidgetsPanel and MainTabPanel are both here to avoir cyclic import diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/main_panel.py --- a/src/browser/sat_browser/main_panel.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/main_panel.py Tue Mar 17 20:42:02 2015 +0100 @@ -36,10 +36,10 @@ from pyjamas.ui import HasVerticalAlignment -import base_menu import menu import dialog import base_widget +import base_menu import libervia_widget import editor_widget import contact_list @@ -168,10 +168,11 @@ def __init__(self, parent): styles = {'menu_bar': 'presence-button'} base_widget.WidgetMenuBar.__init__(self, parent, parent.host, styles=styles) - self.button = self.addCategory(u"◉", u"◉", '') + self.button = self.addCategory(u"◉") + presence_menu = self.button.getSubMenu() for presence, presence_i18n in C.PRESENCE.items(): html = u' %s' % (contact_list.buildPresenceStyle(presence), presence_i18n) - self.addMenuItem([u"◉", presence], [u"◉", html], '', base_menu.MenuCmd(self, 'changePresenceCb', presence), asHTML=True) + presence_menu.addItem(html, True, base_menu.SimpleCmd(lambda presence=presence: self.changePresenceCb(presence))) self.parent_panel = parent def changePresenceCb(self, presence=''): @@ -189,15 +190,16 @@ def __init__(self, host, presence="", status=""): self.host = host + self.plugin_menu_context = [] HorizontalPanel.__init__(self, Width='100%') - self.menu = PresenceStatusMenuBar(self) + self.presence_bar = PresenceStatusMenuBar(self) self.status_panel = StatusPanel(host, status=status) self.setPresence(presence) panel = HorizontalPanel() - panel.add(self.menu) + panel.add(self.presence_bar) panel.add(self.status_panel) - panel.setCellVerticalAlignment(self.menu, 'baseline') + panel.setCellVerticalAlignment(self.presence_bar, 'baseline') panel.setCellVerticalAlignment(self.status_panel, 'baseline') panel.setStyleName("presenceStatusPanel") self.add(panel) @@ -217,7 +219,7 @@ def setPresence(self, presence): self._presence = presence - contact_list.setPresenceStyle(self.menu.button, self._presence) + contact_list.setPresenceStyle(self.presence_bar.button, self._presence) def setStatus(self, status): self.status_panel.setContent({'text': status}) @@ -242,7 +244,7 @@ self.header = VerticalPanel(StyleName="header") self.menu = menu.MainMenuBar(host) self.header.add(self.menu) - + # contacts self.contacts_switch = Button(u'«', self._contactsSwitch) self.contacts_switch.addStyleName('contactsSwitch') @@ -250,7 +252,7 @@ # tab panel self.tab_panel = libervia_widget.MainTabPanel(host) self.tab_panel.addWidgetsTab(_(u"Discussions"), select=True, locked=True) - + # XXX: widget's addition order is important! self.add(self.header, DockPanel.NORTH) self.add(self.tab_panel, DockPanel.CENTER) diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/menu.py --- a/src/browser/sat_browser/menu.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/menu.py Tue Mar 17 20:42:02 2015 +0100 @@ -21,22 +21,17 @@ from sat.core.log import getLogger log = getLogger(__name__) -from sat.core.i18n import _ - -from pyjamas.ui.SimplePanel import SimplePanel from pyjamas.ui.HTML import HTML from pyjamas.ui.Frame import Frame -from pyjamas import Window from constants import Const as C import file_tools import xmlui import chat -import widget import dialog import contact_group import base_menu -from base_menu import MenuCmd +from sat_browser import html_tools unicode = str # FIXME: pyjamas workaround @@ -52,38 +47,13 @@ base_menu.GenericMenuBar.__init__(self, host, vertical=False, styles=styles) @classmethod - def getCategoryHTML(cls, menu_name_i18n, type_): - return cls.ITEM_TPL % (type_, menu_name_i18n) - - def createMenus(self): - self.addMenuItem("General", [_("General"), _("Web widget")], 'home', MenuCmd(self, "onWebWidget")) - self.addMenuItem("General", [_("General"), _("Disconnect")], 'home', MenuCmd(self, "onDisconnect")) - self.addCategory("Contacts", _("Contacts"), 'social') # save the position for this category - self.addMenuItem("Groups", [_("Groups"), _("Discussion")], 'social', MenuCmd(self, "onJoinRoom")) - self.addMenuItem("Groups", [_("Groups"), _("Collective radio")], 'social', MenuCmd(self, "onCollectiveRadio")) - self.addMenuItem("Games", [_("Games"), _("Tarot")], 'games', MenuCmd(self, "onTarotGame")) - self.addMenuItem("Games", [_("Games"), _("Xiangqi")], 'games', MenuCmd(self, "onXiangqiGame")) + def getCategoryHTML(cls, category): + name = html_tools.html_sanitize(category.name) + return cls.ITEM_TPL % (category.icon, name) if category.icon is not None else name - # additional menus - self.addCachedMenus(C.MENU_GLOBAL) - - # menu items that should be displayed after the automatically added ones - self.addMenuItem("Contacts", [_("Contacts"), _("Manage groups")], 'social', MenuCmd(self, "onManageContactGroups")) - - self.addSeparator() - - self.addMenuItem("Help", [_("Help"), _("Social contract")], 'help', MenuCmd(self, "onSocialContract")) - self.addMenuItem("Help", [_("Help"), _("About")], 'help', MenuCmd(self, "onAbout")) - self.addMenuItem("Settings", [_("Settings"), _("Account")], 'settings', MenuCmd(self, "onAccount")) - self.addMenuItem("Settings", [_("Settings"), _("Parameters")], 'settings', MenuCmd(self, "onParameters")) - - # XXX: temporary, will change when a full profile will be managed in SàT - self.addMenuItem("Settings", [_("Settings"), _("Upload avatar")], 'settings', MenuCmd(self, "onAvatarUpload")) + ## callbacks # General menu - def onWebWidget(self): - web_widget = self.host.displayWidget(widget.WebWidget, C.WEB_PANEL_DEFAULT_URL) - self.host.setSelected(web_widget) def onDisconnect(self): def confirm_cb(answer): @@ -92,6 +62,37 @@ _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to disconnect ?") _dialog.show() + #Contact menu + + def onManageContactGroups(self): + """Open the contact groups manager.""" + + def onCloseCallback(): + pass + + contact_group.ContactGroupEditor(self.host, None, onCloseCallback) + + #Group menu + def onJoinRoom(self): + + def invite(room_jid, contacts): + for contact in contacts: + self.host.bridge.call('inviteMUC', None, unicode(contact), unicode(room_jid)) + def join(room_jid, contacts): + if self.host.whoami: + nick = self.host.whoami.node + contact_list = self.host.contact_list + if room_jid is None or room_jid not in contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP): + room_jid_s = unicode(room_jid) if room_jid else '' + self.host.bridge.call('joinMUC', lambda room_jid: invite(room_jid, contacts), room_jid_s, nick) + else: + self.host.displayWidget(chat.Chat, room_jid, type_="group", new_tab=room_jid) + invite(room_jid, contacts) + + dialog.RoomAndContactsChooser(self.host, join, ok_button="Join", visible=(True, False)) + + # Help menu + def onSocialContract(self): _frame = Frame('contrat_social.html') _frame.setStyleName('infoFrame') @@ -112,53 +113,6 @@ _dialog = dialog.GenericDialog("About", _about) _dialog.show() - #Contact menu - def onManageContactGroups(self): - """Open the contact groups manager.""" - - def onCloseCallback(): - pass - - contact_group.ContactGroupEditor(self.host, None, onCloseCallback) - - #Group menu - def onJoinRoom(self): - - def invite(room_jid, contacts): - for contact in contacts: - self.host.bridge.call('inviteMUC', None, unicode(contact), unicode(room_jid)) - - def join(room_jid, contacts): - if self.host.whoami: - nick = self.host.whoami.node - contact_list = self.host.contact_list - if room_jid is None or room_jid not in contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP): - room_jid_s = unicode(room_jid) if room_jid else '' - self.host.bridge.call('joinMUC', lambda room_jid: invite(room_jid, contacts), room_jid_s, nick) - else: - self.host.displayWidget(chat.Chat, room_jid, type_="group", new_tab=room_jid) - invite(room_jid, contacts) - - dialog.RoomAndContactsChooser(self.host, join, ok_button="Join", visible=(True, False)) - - def onCollectiveRadio(self): - def callback(room_jid, contacts): - contacts = [unicode(contact) for contact in contacts] - room_jid_s = unicode(room_jid) if room_jid else '' - self.host.bridge.call('launchRadioCollective', None, contacts, room_jid_s) - dialog.RoomAndContactsChooser(self.host, callback, ok_button="Choose", title="Collective Radio", visible=(False, True)) - - #Game menu - def onTarotGame(self): - def onPlayersSelected(room_jid, other_players): - other_players = [unicode(contact) for contact in other_players] - room_jid_s = unicode(room_jid) if room_jid else '' - self.host.bridge.call('launchTarotGame', None, other_players, room_jid_s) - dialog.RoomAndContactsChooser(self.host, onPlayersSelected, 3, title="Tarot", title_invite="Please select 3 other players", visible=(False, True)) - - def onXiangqiGame(self): - Window.alert("A Xiangqi game is planed, but not available yet") - #Settings menu def onAccount(self): diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/plugin_sec_otr.py --- a/src/browser/sat_browser/plugin_sec_otr.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/plugin_sec_otr.py Tue Mar 17 20:42:02 2015 +0100 @@ -396,7 +396,7 @@ self._gotMenusListener = self.gotMenusListener # FIXME: these listeners are never removed, can't be removed by themselves (it modifies the list while looping), maybe need a 'one_shot' argument self.host.addListener('profilePlugged', self._profilePluggedListener) - self.host.addListener('gotMenus', self._gotMenusListener) + # self.host.addListener('gotMenus', self._gotMenusListener) @classmethod def getInfoText(self, state=otr.context.STATE_PLAINTEXT, trust=''): diff -r 941e53b3af5c -r 849ffb24d5bf src/browser/sat_browser/widget.py --- a/src/browser/sat_browser/widget.py Tue Mar 17 20:28:41 2015 +0100 +++ b/src/browser/sat_browser/widget.py Tue Mar 17 20:42:02 2015 +0100 @@ -21,6 +21,8 @@ from sat.core.log import getLogger log = getLogger(__name__) +from sat.core.i18n import D_ + from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.Button import Button @@ -32,6 +34,7 @@ import libervia_widget from constants import Const as C from sat_frontends.quick_frontend import quick_widgets +from sat_frontends.tools import host_listener # class UniBoxPanel(HorizontalPanel): @@ -242,3 +245,17 @@ if scheme not in C.WEB_PANEL_SCHEMES: url = "http://" + url self._frame.setUrl(url) + + +## Menu + +def hostReady(host): + def onWebWidget(): + web_widget = host.displayWidget(WebWidget, C.WEB_PANEL_DEFAULT_URL) + host.setSelected(web_widget) + + def gotMenus(): + host.menus.addMenu(C.MENU_GLOBAL, (D_(u"General"), D_(u"Web widget")), callback=onWebWidget) + host.addListener('gotMenus', gotMenus) + +host_listener.addListener(hostReady)