Mercurial > libervia-web
diff src/browser/sat_browser/base_menu.py @ 679:a90cc8fc9605
merged branch frontends_multi_profiles
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 18 Mar 2015 16:15:18 +0100 |
parents | 849ffb24d5bf |
children | 9877607c719a |
line wrap: on
line diff
--- a/src/browser/sat_browser/base_menu.py Thu Feb 05 12:05:32 2015 +0100 +++ b/src/browser/sat_browser/base_menu.py Wed Mar 18 16:15:18 2015 +0100 @@ -24,201 +24,45 @@ by base_widget.py, and the import sequence caused a JS runtime error.""" -import pyjd # this is dummy in pyjs 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 -class MenuCmd: - """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 (str): method name if object_ is a class instance - @param data (dict): data to pass as the callback argument - """ - if handler is None: - assert(callable(object_)) - self.callback = object_ - else: - self.callback = getattr(object_, handler) - self.data = data - - def execute(self): - self.callback(self.data) if self.data else self.callback() - - -class PluginMenuCmd: - """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) +unicode = str # FIXME: pyjamas workaround -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".""" +class MenuCmd(object): + """Return an object with an "execute" method that can be set to a menu item callback""" - def __init__(self, name=None, item=None, menu=None, flat_level=0): - """ - @param name (str): 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[str]): path to the category - @param path_i18n (list[str]): internationalized path to the category - @param types (list[str]): 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 + def __init__(self, menu_item, caller=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[str]): path to the target node - @return: list[MenuNode] + @param menu_item(quick_menu.MenuItem): instance of a callbable MenuItem + @param caller: menu caller """ - 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[str], str): path to the item - @param path_i18n (list[str], str): internationalized path to the item - @param types (list[str], str): 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)) + self.item = menu_item + self._caller = caller - leaf_node = hasattr(callback, "execute") - category = isinstance(callback, GenericMenuBar) - assert(not leaf_node or not category) - - path = [path] if isinstance(path, str) else path - path_i18n = [path_i18n] if isinstance(path_i18n, str) else path_i18n - types = [types for dummy in range(len(path_i18n))] if isinstance(types, str) 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 execute(self): + self.item.call(self._caller) - def addCategory(self, path, path_i18n, types, menu_bar=None): - """Recursively add a new category. - @param path (list[str], str): path to the category - @param path_i18n (list[str], str): internationalized path to the category - @param types (list[str], str): types of the category and its parents - @param menu_bar (GenericMenuBar): instance to popup as the category sub-menu. - """ - 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. +class SimpleCmd(object): + """Return an object with an "executre" method that launch a callback""" - @param item: see MenuBar.addItem - @param asHTML: see MenuBar.addItem - @param popup: see MenuBar.addItem - @param name (str): the item node's name + def __init__(self, callback): """ - 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"]) + @param callback: method to call when menu is selected + """ + self.callback = callback - 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, str): - 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): @@ -236,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 (str): internationalized category name - @param type_ (str): category type - @return: str + @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""" @@ -294,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): - 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