diff browser/sat_browser/base_menu.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/browser/sat_browser/base_menu.py@f2170536ba23
children 2af117bfe6cc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/browser/sat_browser/base_menu.py	Sat Aug 25 17:59:48 2018 +0200
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2011-2018 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."""
+
+
+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
+from sat_frontends.quick_frontend import quick_menus
+from sat_browser import html_tools
+
+
+unicode = str  # FIXME: pyjamas workaround
+
+
+class MenuCmd(object):
+    """Return an object with an "execute" method that can be set to a menu item callback"""
+
+    def __init__(self, menu_item, caller=None):
+        """
+        @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):
+        self.item.call(self._caller)
+
+
+class SimpleCmd(object):
+    """Return an object with an "executre" method that launch a callback"""
+
+    def __init__(self, callback):
+        """
+        @param callback: method to call when menu is selected
+        """
+        self.callback = callback
+
+    def execute(self):
+        self.callback()
+
+
+class GenericMenuBar(MenuBar):
+    """A menu bar with sub-categories and items"""
+
+    def __init__(self, host, vertical=False, styles=None, flat_level=0, **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 name
+        @param flat_level (int): sub-menus until that level see their items
+        displayed in the parent menu bar instead of in a callback popup.
+        """
+        MenuBar.__init__(self, vertical, **kwargs)
+        self.host = host
+        self.styles = {}
+        if styles:
+            self.styles.update(styles)
+        try:
+            self.setStyleName(self.styles['menu_bar'])
+        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, category):
+        """Build the html to be used for displaying a category item.
+
+        Inheriting classes may overwrite this method.
+        @param category (quick_menus.MenuCategory): category to add
+        @return unicode: HTML to display
+        """
+        return html_tools.html_sanitize(category.name)
+
+    def _buildMenus(self, container, flat_level, caller=None):
+        """Recursively build menus of the container
+
+        @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 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"""
+        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
+            try:
+                self.popup.addStyleName(self.styles['moved_popup'])
+            except KeyError:
+                pass
+
+    def addCategory(self, category, menu_bar=None, flat=False):
+        """Add a new category.
+
+        @param category (quick_menus.MenuCategory): category to add
+        @param menu_bar (GenericMenuBar): instance to popup as the category sub-menu.
+        """
+        html = self.getCategoryHTML(category)
+
+        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
+
+        item = self.addItem(html, True, sub_menu)
+        if flat:
+            item.setStyleName("menuFlattenedCategory")
+        return item