Mercurial > libervia-web
comparison 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 |
comparison
equal
deleted
inserted
replaced
497:516b06787c1a | 498:60be99de3808 |
---|---|
28 from sat.core.log import getLogger | 28 from sat.core.log import getLogger |
29 log = getLogger(__name__) | 29 log = getLogger(__name__) |
30 | 30 |
31 from pyjamas.ui.MenuBar import MenuBar | 31 from pyjamas.ui.MenuBar import MenuBar |
32 from pyjamas.ui.MenuItem import MenuItem | 32 from pyjamas.ui.MenuItem import MenuItem |
33 from pyjamas import Window | |
33 | 34 |
34 | 35 |
35 class MenuCmd: | 36 class MenuCmd: |
36 """Return an object with an "execute" method that can be set to a menu item callback""" | 37 """Return an object with an "execute" method that can be set to a menu item callback""" |
37 | 38 |
52 self.action_id = action_id | 53 self.action_id = action_id |
53 self.menu_data = menu_data | 54 self.menu_data = menu_data |
54 | 55 |
55 def execute(self): | 56 def execute(self): |
56 self.host.launchAction(self.action_id, self.menu_data) | 57 self.host.launchAction(self.action_id, self.menu_data) |
57 | |
58 | |
59 class CategoryMenuBar(MenuBar): | |
60 """A menu bar for a category (sub menu)""" | |
61 def __init__(self): | |
62 MenuBar.__init__(self, vertical=True) | |
63 | 58 |
64 | 59 |
65 class CategoryItem(MenuItem): | 60 class CategoryItem(MenuItem): |
66 """A category item with a non-internationalized name""" | 61 """A category item with a non-internationalized name""" |
67 def __init__(self, name, *args, **kwargs): | 62 def __init__(self, name, *args, **kwargs): |
68 MenuItem.__init__(self, *args, **kwargs) | 63 MenuItem.__init__(self, *args, **kwargs) |
69 self.name = name | 64 self.name = name |
70 | 65 |
71 | 66 |
72 class GenericMenuBar(MenuBar): | 67 class GenericMenuBar(MenuBar): |
73 | 68 """A menu bar with sub-categories and items""" |
74 def __init__(self, host, vertical=False, **kwargs): | 69 |
70 def __init__(self, host, vertical=False, styles=None, **kwargs): | |
71 """ | |
72 @param host (SatWebFrontend): host instance | |
73 @param vertical (bool): True to display the popup menu vertically | |
74 @param styles (dict): specific styles to be applied: | |
75 - key: a value in ('moved_popup', 'menu_bar') | |
76 - value: a CSS class | |
77 the popup that are not displayed at the position computed by pyjamas. | |
78 """ | |
75 MenuBar.__init__(self, vertical, **kwargs) | 79 MenuBar.__init__(self, vertical, **kwargs) |
76 self.host = host | 80 self.host = host |
77 self.moved_popup_style = None | 81 self.styles = styles or {} |
82 if 'menu_bar' in self.styles: | |
83 # XXX: pyjamas set the style to object string representation! | |
84 # FIXME: fix the bug upstream | |
85 first = 'gwt-MenuBar' | |
86 second = first + '-' + ('vertical' if self.vertical else 'horizontal') | |
87 self.setStyleName(' '.join([first, second, self.styles['menu_bar']])) | |
78 | 88 |
79 @classmethod | 89 @classmethod |
80 def getCategoryHTML(cls, type_, menu_name_i18n): | 90 def getCategoryHTML(cls, type_, menu_name_i18n): |
81 """Build from the given parameters the html to be displayed for a category item. | 91 """Build the html to be used for displaying a category item. |
82 | 92 |
83 Inheriting classes may overwrite this method. | 93 Inheriting classes may overwrite this method. |
84 @param type_ (str): category type | 94 @param type_ (str): category type |
85 @param menu_name_i18n (str): internationalized category name | 95 @param menu_name_i18n (str): internationalized category name |
86 @return: str | 96 @return: str |
88 return menu_name_i18n | 98 return menu_name_i18n |
89 | 99 |
90 def doItemAction(self, item, fireCommand): | 100 def doItemAction(self, item, fireCommand): |
91 """Overwrites the default behavior for the popup menu to fit in the screen""" | 101 """Overwrites the default behavior for the popup menu to fit in the screen""" |
92 MenuBar.doItemAction(self, item, fireCommand) | 102 MenuBar.doItemAction(self, item, fireCommand) |
93 if not self.vertical and self.popup: | 103 if not self.popup: |
94 # we not only move the last popup, but any which would go over the menu right extremity | 104 return |
95 most_left = self.getAbsoluteLeft() + self.getOffsetWidth() - self.popup.getOffsetWidth() | 105 if self.vertical: |
96 if item.getAbsoluteLeft() > most_left: | 106 # move the popup if it would go over the screen's viewport |
97 self.popup.setPopupPosition(most_left, | 107 max_left = Window.getClientWidth() - self.getOffsetWidth() + 1 - self.popup.getOffsetWidth() |
98 self.getAbsoluteTop() + | 108 new_left = self.getAbsoluteLeft() - self.popup.getOffsetWidth() + 1 |
99 self.getOffsetHeight() - 1) | 109 top = item.getAbsoluteTop() |
100 # eventually smooth the popup edges to fit the menu own style | 110 else: |
101 if self.moved_popup_style: | 111 # move the popup if it would go over the menu bar right extremity |
102 self.popup.addStyleName(self.moved_popup_style) | 112 max_left = self.getAbsoluteLeft() + self.getOffsetWidth() - self.popup.getOffsetWidth() |
113 new_left = max_left | |
114 top = self.getAbsoluteTop() + self.getOffsetHeight() - 1 | |
115 if item.getAbsoluteLeft() > max_left: | |
116 self.popup.setPopupPosition(new_left, top) | |
117 # eventually smooth the popup edges to fit the menu own style | |
118 if 'moved_popup' in self.styles: | |
119 self.popup.addStyleName(self.styles['moved_popup']) | |
103 | 120 |
104 def getCategories(self): | 121 def getCategories(self): |
105 """Return the categories items. | 122 """Return all the categories items. |
106 | 123 |
107 @return: list[CategoryItem] | 124 @return: list[CategoryItem] |
108 """ | 125 """ |
109 return [item for item in self.items if isinstance(item, CategoryItem)] | 126 return [item for item in self.items if isinstance(item, CategoryItem)] |
110 | 127 |
111 def getSubMenu(self, category): | 128 def getCategoryItem(self, path): |
129 """Return the requested category item | |
130 | |
131 @param path (list[str]): path to the category | |
132 @return: CategoryInstance or None | |
133 """ | |
134 assert(len(path) > 0) | |
135 if len(path) > 1: | |
136 menu = self.getCategoryMenu(path[:1]) | |
137 return menu.getCategoryItem(path[1:]) if menu else None | |
138 items = [item for item in self.items if isinstance(item, CategoryItem) and item.name == path[0]] | |
139 if len(items) == 1: | |
140 return items[0] | |
141 assert(items == []) # there should not be more than 1 category with the same name | |
142 return None | |
143 | |
144 def getCategoryMenu(self, path): | |
112 """Return the popup menu for the given category | 145 """Return the popup menu for the given category |
113 | 146 |
114 @param category (str): category name | 147 @param path (list[str]): path to the category |
115 @return: CategoryMenuBar instance or None | 148 @return: CategoryMenuBar instance or None |
116 """ | 149 """ |
117 try: | 150 item = self.getCategoryItem(path) |
118 return [item for item in self.items if isinstance(item, CategoryItem) and item.name == category][0].getSubMenu() | 151 return item.getSubMenu() if item else None |
119 except IndexError: | |
120 return None | |
121 | 152 |
122 def addSeparator(self): | 153 def addSeparator(self): |
123 """Add a separator between the categories""" | 154 """Add a separator between the categories""" |
124 self.addItem(CategoryItem(None, text='', asHTML=None, StyleName='menuSeparator')) | 155 self.addItem(CategoryItem(None, text='', asHTML=None, StyleName='menuSeparator')) |
125 | 156 |
126 def addCategory(self, menu_name, menu_name_i18n, type_, sub_menu): | 157 def addCategory(self, path, path_i18n, type_, sub_menu=None): |
127 """Add a category | 158 """Add a category item and its associated sub-menu. |
128 | 159 |
129 @param menu_name (str): category name | 160 If the category already exists, do not overwrite the current sub-menu. |
130 @param menu_name_i18n (str): internationalized category name | 161 @param path (list[str], str): path to the category. Passing a string for |
162 the category name is also accepted if there's no sub-category. | |
163 @param path_i18n (list[str], str): internationalized path to the category. | |
164 Passing a string for the internationalized category name is also accepted | |
165 if there's no sub-category. | |
131 @param type_ (str): category type | 166 @param type_ (str): category type |
132 @param sub_menu (CategoryMenuBar): category sub-menu | 167 @param sub_menu (CategoryMenuBar): category sub-menu |
133 """ | 168 """ |
134 html = self.getCategoryHTML(type_, menu_name_i18n) | 169 if isinstance(path, str): |
135 self.addItem(CategoryItem(menu_name, text=html, asHTML=True, subMenu=sub_menu)) | 170 path = [path] |
136 | 171 if isinstance(path_i18n, str): |
137 def addMenu(self, menu_name, menu_name_i18n, item_name_i18n, type_, menu_cmd): | 172 path_i18n = [path_i18n] |
173 assert(len(path) > 0 and len(path) == len(path_i18n)) | |
174 current = self | |
175 count = len(path) | |
176 for menu_name, menu_name_i18n in zip(path, path_i18n): | |
177 tmp = current.getCategoryMenu([menu_name]) | |
178 if not tmp: | |
179 html = self.getCategoryHTML(type_, menu_name_i18n) | |
180 tmp = CategoryMenuBar(self.host) if (count > 1 or not sub_menu) else sub_menu | |
181 current.addItem(CategoryItem(menu_name, text=html, asHTML=True, subMenu=tmp)) | |
182 current = tmp | |
183 count -= 1 | |
184 | |
185 def addMenuItem(self, path, path_i18n, type_, menu_cmd): | |
138 """Add a new menu item | 186 """Add a new menu item |
139 @param menu_name (str): category name | 187 @param path (list[str], str): path to the category, completed by a dummy |
140 @param menu_name_i18n (str): internationalized menu name | 188 value for the item in last position. Passing a string for the category |
141 @param item_name_i18n (str): internationalized item name | 189 name is also accepted if there's no sub-category. |
190 @param path_i18n (list[str]): internationalized path to the item | |
142 @param type_ (str): category type in ('games', 'help', 'home', 'photos', 'plugins', 'settings', 'social') | 191 @param type_ (str): category type in ('games', 'help', 'home', 'photos', 'plugins', 'settings', 'social') |
143 @param menu_cmd (MenuCmd or PluginMenuCmd): instance to execute as the item callback | 192 @param menu_cmd (MenuCmd or PluginMenuCmd): instance to execute as the item callback |
144 """ | 193 """ |
145 log.info("addMenu: %s %s %s %s %s" % (menu_name, menu_name_i18n, item_name_i18n, type_, menu_cmd)) | 194 if isinstance(path, str): |
146 sub_menu = self.getSubMenu(menu_name) | 195 assert(len(path_i18n) == 2) |
196 path = [path, None] | |
197 assert(len(path) > 1 and len(path) == len(path_i18n)) | |
198 log.info("addMenuItem: %s %s %s %s" % (path, path_i18n, type_, menu_cmd)) | |
199 sub_menu = self.getCategoryMenu(path[:-1]) | |
147 if not sub_menu: | 200 if not sub_menu: |
148 sub_menu = CategoryMenuBar() | 201 sub_menu = CategoryMenuBar(self.host) |
149 self.addCategory(menu_name, menu_name_i18n, type_, sub_menu) | 202 self.addCategory(path[:-1], path_i18n[:-1], type_, sub_menu) |
150 if item_name_i18n and menu_cmd: | 203 if menu_cmd: |
151 sub_menu.addItem(item_name_i18n, menu_cmd) | 204 sub_menu.addItem(path_i18n[-1], menu_cmd) |
152 | 205 |
153 def addCachedMenus(self, type_, menu_data=None): | 206 def addCachedMenus(self, type_, menu_data=None): |
154 """Add cached menus to instance | 207 """Add cached menus to instance |
155 @param type_: menu type like is sat.core.sat_main.importMenu | 208 @param type_: menu type like is sat.core.sat_main.importMenu |
156 @param menu_data: data to send with these menus | 209 @param menu_data: data to send with these menus |
157 """ | 210 """ |
158 menus = self.host.menus.get(type_, []) | 211 menus = self.host.menus.get(type_, []) |
159 for action_id, path, path_i18n in menus: | 212 for action_id, path, path_i18n in menus: |
160 if len(path) != 2: | |
161 raise NotImplementedError("Menu with a path != 2 are not implemented yet") | |
162 if len(path) != len(path_i18n): | 213 if len(path) != len(path_i18n): |
163 log.error("inconsistency between menu paths") | 214 log.error("inconsistency between menu paths") |
164 continue | 215 continue |
165 callback = PluginMenuCmd(self.host, action_id, menu_data) | 216 callback = PluginMenuCmd(self.host, action_id, menu_data) |
166 self.addMenu(path[0], path_i18n[0], path_i18n[1], 'plugins', callback) | 217 self.addMenuItem(path, path_i18n, 'plugins', callback) |
218 | |
219 | |
220 class CategoryMenuBar(GenericMenuBar): | |
221 """A menu bar for a category (sub-menu)""" | |
222 def __init__(self, host): | |
223 GenericMenuBar.__init__(self, host, vertical=True) |