Mercurial > libervia-web
view src/browser/sat_browser/base_menu.py @ 498:60be99de3808
browser_side: menus refactorization + handle levels > 2
author | souliane <souliane@mailoo.org> |
---|---|
date | Fri, 25 Jul 2014 02:38:30 +0200 |
parents | 5d8632a7bfde |
children | 4aa627b059df |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # Libervia: a Salut à Toi frontend # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """Base classes for building a menu. These classes have been moved here from menu.py because they are also used 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 pyjamas.ui.MenuBar import MenuBar from pyjamas.ui.MenuItem import MenuItem from pyjamas import Window class MenuCmd: """Return an object with an "execute" method that can be set to a menu item callback""" def __init__(self, object_, handler): self._object = object_ self._handler = handler def execute(self): handler = getattr(self._object, self._handler) handler() 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) class CategoryItem(MenuItem): """A category item with a non-internationalized name""" def __init__(self, name, *args, **kwargs): MenuItem.__init__(self, *args, **kwargs) self.name = name class GenericMenuBar(MenuBar): """A menu bar with sub-categories and items""" def __init__(self, host, vertical=False, styles=None, **kwargs): """ @param host (SatWebFrontend): host instance @param vertical (bool): True to display the popup menu vertically @param styles (dict): specific styles to be applied: - key: a value in ('moved_popup', 'menu_bar') - value: a CSS class the popup that are not displayed at the position computed by pyjamas. """ MenuBar.__init__(self, vertical, **kwargs) self.host = host self.styles = styles or {} if 'menu_bar' in self.styles: # XXX: pyjamas set the style to object string representation! # FIXME: fix the bug upstream first = 'gwt-MenuBar' second = first + '-' + ('vertical' if self.vertical else 'horizontal') self.setStyleName(' '.join([first, second, self.styles['menu_bar']])) @classmethod def getCategoryHTML(cls, type_, menu_name_i18n): """Build the html to be used for displaying a category item. Inheriting classes may overwrite this method. @param type_ (str): category type @param menu_name_i18n (str): internationalized category name @return: str """ return menu_name_i18n def doItemAction(self, item, fireCommand): """Overwrites the default behavior for the popup menu to fit in the screen""" MenuBar.doItemAction(self, item, fireCommand) if not self.popup: return if self.vertical: # move the popup if it would go over the screen's viewport max_left = Window.getClientWidth() - self.getOffsetWidth() + 1 - self.popup.getOffsetWidth() new_left = self.getAbsoluteLeft() - self.popup.getOffsetWidth() + 1 top = item.getAbsoluteTop() else: # move the popup if it would go over the menu bar right extremity max_left = self.getAbsoluteLeft() + self.getOffsetWidth() - self.popup.getOffsetWidth() new_left = max_left top = self.getAbsoluteTop() + self.getOffsetHeight() - 1 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: self.popup.addStyleName(self.styles['moved_popup']) def getCategories(self): """Return all the categories items. @return: list[CategoryItem] """ return [item for item in self.items if isinstance(item, CategoryItem)] def getCategoryItem(self, path): """Return the requested category item @param path (list[str]): path to the category @return: CategoryInstance or None """ assert(len(path) > 0) if len(path) > 1: menu = self.getCategoryMenu(path[:1]) return menu.getCategoryItem(path[1:]) if menu else None items = [item for item in self.items if isinstance(item, CategoryItem) and item.name == path[0]] if len(items) == 1: return items[0] assert(items == []) # there should not be more than 1 category with the same name return None def getCategoryMenu(self, path): """Return the popup menu for the given category @param path (list[str]): path to the category @return: CategoryMenuBar instance or None """ item = self.getCategoryItem(path) return item.getSubMenu() if item else None def addSeparator(self): """Add a separator between the categories""" self.addItem(CategoryItem(None, text='', asHTML=None, StyleName='menuSeparator')) def addCategory(self, path, path_i18n, type_, sub_menu=None): """Add a category item and its associated sub-menu. If the category already exists, do not overwrite the current sub-menu. @param path (list[str], str): path to the category. Passing a string for the category name is also accepted if there's no sub-category. @param path_i18n (list[str], str): internationalized path to the category. Passing a string for the internationalized category name is also accepted if there's no sub-category. @param type_ (str): category type @param sub_menu (CategoryMenuBar): category sub-menu """ if isinstance(path, str): path = [path] if isinstance(path_i18n, str): path_i18n = [path_i18n] assert(len(path) > 0 and len(path) == len(path_i18n)) current = self count = len(path) for menu_name, menu_name_i18n in zip(path, path_i18n): tmp = current.getCategoryMenu([menu_name]) if not tmp: html = self.getCategoryHTML(type_, menu_name_i18n) tmp = CategoryMenuBar(self.host) if (count > 1 or not sub_menu) else sub_menu current.addItem(CategoryItem(menu_name, text=html, asHTML=True, subMenu=tmp)) current = tmp count -= 1 def addMenuItem(self, path, path_i18n, type_, menu_cmd): """Add a new menu item @param path (list[str], str): path to the category, completed by a dummy value for the item in last position. Passing a string for the category name is also accepted if there's no sub-category. @param path_i18n (list[str]): internationalized path to the item @param type_ (str): category type in ('games', 'help', 'home', 'photos', 'plugins', 'settings', 'social') @param menu_cmd (MenuCmd or PluginMenuCmd): instance to execute as the item callback """ if isinstance(path, str): assert(len(path_i18n) == 2) path = [path, None] assert(len(path) > 1 and len(path) == len(path_i18n)) log.info("addMenuItem: %s %s %s %s" % (path, path_i18n, type_, menu_cmd)) sub_menu = self.getCategoryMenu(path[:-1]) if not sub_menu: sub_menu = CategoryMenuBar(self.host) self.addCategory(path[:-1], path_i18n[:-1], type_, sub_menu) if menu_cmd: sub_menu.addItem(path_i18n[-1], menu_cmd) def addCachedMenus(self, type_, menu_data=None): """Add cached menus to instance @param type_: menu type like is sat.core.sat_main.importMenu @param menu_data: data to send with these menus """ menus = self.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 callback = PluginMenuCmd(self.host, action_id, menu_data) self.addMenuItem(path, path_i18n, 'plugins', callback) class CategoryMenuBar(GenericMenuBar): """A menu bar for a category (sub-menu)""" def __init__(self, host): GenericMenuBar.__init__(self, host, vertical=True)