comparison 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
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
21 """Base classes for building a menu.
22
23 These classes have been moved here from menu.py because they are also used
24 by base_widget.py, and the import sequence caused a JS runtime error."""
25
26
27 from sat.core.log import getLogger
28 log = getLogger(__name__)
29
30 from pyjamas.ui.MenuBar import MenuBar
31 from pyjamas.ui.MenuItem import MenuItem
32 from pyjamas import Window
33 from sat_frontends.quick_frontend import quick_menus
34 from sat_browser import html_tools
35
36
37 unicode = str # FIXME: pyjamas workaround
38
39
40 class MenuCmd(object):
41 """Return an object with an "execute" method that can be set to a menu item callback"""
42
43 def __init__(self, menu_item, caller=None):
44 """
45 @param menu_item(quick_menu.MenuItem): instance of a callbable MenuItem
46 @param caller: menu caller
47 """
48 self.item = menu_item
49 self._caller = caller
50
51 def execute(self):
52 self.item.call(self._caller)
53
54
55 class SimpleCmd(object):
56 """Return an object with an "executre" method that launch a callback"""
57
58 def __init__(self, callback):
59 """
60 @param callback: method to call when menu is selected
61 """
62 self.callback = callback
63
64 def execute(self):
65 self.callback()
66
67
68 class GenericMenuBar(MenuBar):
69 """A menu bar with sub-categories and items"""
70
71 def __init__(self, host, vertical=False, styles=None, flat_level=0, **kwargs):
72 """
73 @param host (SatWebFrontend): host instance
74 @param vertical (bool): True to display the popup menu vertically
75 @param styles (dict): specific styles to be applied:
76 - key: a value in ('moved_popup', 'menu_bar')
77 - value: a CSS class name
78 @param flat_level (int): sub-menus until that level see their items
79 displayed in the parent menu bar instead of in a callback popup.
80 """
81 MenuBar.__init__(self, vertical, **kwargs)
82 self.host = host
83 self.styles = {}
84 if styles:
85 self.styles.update(styles)
86 try:
87 self.setStyleName(self.styles['menu_bar'])
88 except KeyError:
89 pass
90 self.menus_container = None
91 self.flat_level = flat_level
92
93 def update(self, type_, caller=None):
94 """Method to call when menus have changed
95
96 @param type_: menu type like in sat.core.sat_main.importMenu
97 @param caller: instance linked to the menus
98 """
99 self.menus_container = self.host.menus.getMainContainer(type_)
100 self._caller=caller
101 self.createMenus()
102
103 @classmethod
104 def getCategoryHTML(cls, category):
105 """Build the html to be used for displaying a category item.
106
107 Inheriting classes may overwrite this method.
108 @param category (quick_menus.MenuCategory): category to add
109 @return unicode: HTML to display
110 """
111 return html_tools.html_sanitize(category.name)
112
113 def _buildMenus(self, container, flat_level, caller=None):
114 """Recursively build menus of the container
115
116 @param container: a quick_menus.MenuContainer instance
117 @param caller: instance linked to the menus
118 """
119 for child in container.getActiveMenus():
120 if isinstance(child, quick_menus.MenuContainer):
121 item = self.addCategory(child, flat=bool(flat_level))
122 submenu = item.getSubMenu()
123 if submenu is None:
124 submenu = self
125 submenu._buildMenus(child, flat_level-1 if flat_level else 0, caller)
126 elif isinstance(child, quick_menus.MenuSeparator):
127 item = MenuItem(text='', asHTML=None, StyleName="menuSeparator")
128 self.addItem(item)
129 elif isinstance(child, quick_menus.MenuItem):
130 self.addItem(child.name, False, MenuCmd(child, caller) if child.CALLABLE else None)
131 else:
132 log.error(u"Unknown child type: {}".format(child))
133
134 def createMenus(self):
135 self.clearItems()
136 if self.menus_container is None:
137 log.debug("Menu is empty")
138 return
139 self._buildMenus(self.menus_container, self.flat_level, self._caller)
140
141 def doItemAction(self, item, fireCommand):
142 """Overwrites the default behavior for the popup menu to fit in the screen"""
143 MenuBar.doItemAction(self, item, fireCommand)
144 if not self.popup:
145 return
146 if self.vertical:
147 # move the popup if it would go over the screen's viewport
148 max_left = Window.getClientWidth() - self.getOffsetWidth() + 1 - self.popup.getOffsetWidth()
149 new_left = self.getAbsoluteLeft() - self.popup.getOffsetWidth() + 1
150 top = item.getAbsoluteTop()
151 else:
152 # move the popup if it would go over the menu bar right extremity
153 max_left = self.getAbsoluteLeft() + self.getOffsetWidth() - self.popup.getOffsetWidth()
154 new_left = max_left
155 top = self.getAbsoluteTop() + self.getOffsetHeight() - 1
156 if item.getAbsoluteLeft() > max_left:
157 self.popup.setPopupPosition(new_left, top)
158 # eventually smooth the popup edges to fit the menu own style
159 try:
160 self.popup.addStyleName(self.styles['moved_popup'])
161 except KeyError:
162 pass
163
164 def addCategory(self, category, menu_bar=None, flat=False):
165 """Add a new category.
166
167 @param category (quick_menus.MenuCategory): category to add
168 @param menu_bar (GenericMenuBar): instance to popup as the category sub-menu.
169 """
170 html = self.getCategoryHTML(category)
171
172 if menu_bar is not None:
173 assert not flat # can't have a menu_bar and be flat at the same time
174 sub_menu = menu_bar
175 elif not flat:
176 sub_menu = GenericMenuBar(self.host, vertical=True)
177 else:
178 sub_menu = None
179
180 item = self.addItem(html, True, sub_menu)
181 if flat:
182 item.setStyleName("menuFlattenedCategory")
183 return item