Mercurial > libervia-web
diff src/browser/sat_browser/base_menu.py @ 676:849ffb24d5bf frontends_multi_profiles
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 17 Mar 2015 20:42:02 +0100 |
parents | ebb602d8b3f2 |
children | 9877607c719a |
line wrap: on
line diff
--- 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