# HG changeset patch # User souliane # Date 1405442635 -7200 # Node ID 5d8632a7bfde7723e97ead46263d5afc65bb7e0c # Parent 636b6c477a8754eeccdc9a2e981e92a43e2f51d8 browser_side: refactorisation of menus and LiberviaWidget's header diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/libervia_main.py --- a/src/browser/libervia_main.py Tue Jul 15 13:39:36 2014 +0200 +++ b/src/browser/libervia_main.py Tue Jul 15 18:43:55 2014 +0200 @@ -191,7 +191,7 @@ self.notification = notification.Notification() DOM.addEventPreview(self) self._register = RegisterCall() - self._register.call('getMenus', self.panel.menu.createMenus) + self._register.call('getMenus', self.gotMenus) self._register.call('registerParams', None) self._register.call('isRegistered', self._isRegisteredCB) self.initialised = False @@ -316,6 +316,18 @@ def displayNotification(self, title, body): self.notification.notify(title, body) + def gotMenus(self, menus): + """Put the menus data in cache and build the main menu bar + + @param menus (list[tuple]): menu data + """ + self.menus = {} + for raw_menu in menus: + id_, type_, path, path_i18n = raw_menu + menus_data = self.menus.setdefault(type_, []) + menus_data.append((id_, path, path_i18n)) + self.panel.menu.createMenus() + def _isRegisteredCB(self, result): registered, warning = result if not registered: diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/public/libervia.css --- a/src/browser/public/libervia.css Tue Jul 15 13:39:36 2014 +0200 +++ b/src/browser/public/libervia.css Tue Jul 15 18:43:55 2014 +0200 @@ -720,20 +720,14 @@ height: 20px; padding-top: 2px; padding-bottom: 3px; + padding-right: 0px; border-left: 1px solid #666; border-top: 0; - border-radius: 0 10px 0 0; + border-radius: 0 0 0 0; background: -webkit-gradient(linear, left top, left bottom, from(#555), to(#333)); background: -webkit-linear-gradient(top, #555, #333); background: linear-gradient(to bottom, #555, #333); -} - -.widgetHeader_closeButton { - border-radius: 0 10px 0 0 !important; -} - -.widgetHeader_settingButton { - border-radius: 0 0 0 0 !important; + cursor: pointer; } .widgetHeader_buttonGroup img:hover { diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/sat_browser/base_menu.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/browser/sat_browser/base_menu.py Tue Jul 15 18:43:55 2014 +0200 @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Libervia: a Salut à Toi frontend +# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson + +# 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 . + + +"""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 + + +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 CategoryMenuBar(MenuBar): + """A menu bar for a category (sub menu)""" + def __init__(self): + MenuBar.__init__(self, vertical=True) + + +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): + + def __init__(self, host, vertical=False, **kwargs): + MenuBar.__init__(self, vertical, **kwargs) + self.host = host + self.moved_popup_style = None + + @classmethod + def getCategoryHTML(cls, type_, menu_name_i18n): + """Build from the given parameters the html to be displayed for 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.vertical and self.popup: + # we not only move the last popup, but any which would go over the menu right extremity + most_left = self.getAbsoluteLeft() + self.getOffsetWidth() - self.popup.getOffsetWidth() + if item.getAbsoluteLeft() > most_left: + self.popup.setPopupPosition(most_left, + self.getAbsoluteTop() + + self.getOffsetHeight() - 1) + # eventually smooth the popup edges to fit the menu own style + if self.moved_popup_style: + self.popup.addStyleName(self.moved_popup_style) + + def getCategories(self): + """Return the categories items. + + @return: list[CategoryItem] + """ + return [item for item in self.items if isinstance(item, CategoryItem)] + + def getSubMenu(self, category): + """Return the popup menu for the given category + + @param category (str): category name + @return: CategoryMenuBar instance or None + """ + try: + return [item for item in self.items if isinstance(item, CategoryItem) and item.name == category][0].getSubMenu() + except IndexError: + return None + + def addSeparator(self): + """Add a separator between the categories""" + self.addItem(CategoryItem(None, text='', asHTML=None, StyleName='menuSeparator')) + + def addCategory(self, menu_name, menu_name_i18n, type_, sub_menu): + """Add a category + + @param menu_name (str): category name + @param menu_name_i18n (str): internationalized category name + @param type_ (str): category type + @param sub_menu (CategoryMenuBar): category sub-menu + """ + html = self.getCategoryHTML(type_, menu_name_i18n) + self.addItem(CategoryItem(menu_name, text=html, asHTML=True, subMenu=sub_menu)) + + def addMenu(self, menu_name, menu_name_i18n, item_name_i18n, type_, menu_cmd): + """Add a new menu item + @param menu_name (str): category name + @param menu_name_i18n (str): internationalized menu name + @param item_name_i18n (str): internationalized item name + @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 + """ + log.info("addMenu: %s %s %s %s %s" % (menu_name, menu_name_i18n, item_name_i18n, type_, menu_cmd)) + sub_menu = self.getSubMenu(menu_name) + if not sub_menu: + sub_menu = CategoryMenuBar() + self.addCategory(menu_name, menu_name_i18n, type_, sub_menu) + if item_name_i18n and menu_cmd: + sub_menu.addItem(item_name_i18n, 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) != 2: + raise NotImplementedError("Menu with a path != 2 are not implemented yet") + if len(path) != len(path_i18n): + log.error("inconsistency between menu paths") + continue + callback = PluginMenuCmd(self.host, action_id, menu_data) + self.addMenu(path[0], path_i18n[0], path_i18n[1], 'plugins', callback) diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/sat_browser/base_widget.py --- a/src/browser/sat_browser/base_widget.py Tue Jul 15 13:39:36 2014 +0200 +++ b/src/browser/sat_browser/base_widget.py Tue Jul 15 18:43:55 2014 +0200 @@ -23,14 +23,12 @@ from pyjamas.ui.SimplePanel import SimplePanel from pyjamas.ui.AbsolutePanel import AbsolutePanel from pyjamas.ui.VerticalPanel import VerticalPanel -from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.ScrollPanel import ScrollPanel from pyjamas.ui.FlexTable import FlexTable from pyjamas.ui.TabPanel import TabPanel from pyjamas.ui.HTMLPanel import HTMLPanel from pyjamas.ui.Label import Label from pyjamas.ui.Button import Button -from pyjamas.ui.Image import Image from pyjamas.ui.Widget import Widget from pyjamas.ui.DragWidget import DragWidget from pyjamas.ui.DropWidget import DropWidget @@ -38,9 +36,11 @@ from pyjamas.ui import HasAlignment from pyjamas import DOM from pyjamas import Window + from __pyjamas__ import doc import dialog +import base_menu class DragLabel(DragWidget): @@ -165,23 +165,30 @@ #FIXME: delete object ? Check the right way with pyjamas +class WidgetMenuBar(base_menu.GenericMenuBar): + + ITEM_TPL = "" + + def __init__(self, host, vertical=False): + base_menu.GenericMenuBar.__init__(self, host, vertical=vertical) + self.setStyleName('widgetHeader_buttonGroup') + + @classmethod + def getCategoryHTML(cls, type_, menu_name_i18n): + return cls.ITEM_TPL % type_ + + class WidgetHeader(AbsolutePanel, LiberviaDragWidget): - def __init__(self, parent, title): + def __init__(self, parent, host, title): AbsolutePanel.__init__(self) self.add(title) button_group_wrapper = SimplePanel() button_group_wrapper.setStyleName('widgetHeader_buttonsWrapper') - button_group = HorizontalPanel() - button_group.setStyleName('widgetHeader_buttonGroup') - setting_button = Image("media/icons/misc/settings.png") - setting_button.setStyleName('widgetHeader_settingButton') - setting_button.addClickListener(parent.onSetting) - close_button = Image("media/icons/misc/close.png") - close_button.setStyleName('widgetHeader_closeButton') - close_button.addClickListener(parent.onClose) - button_group.add(setting_button) - button_group.add(close_button) + button_group = WidgetMenuBar(host) + parent.addCachedMenus(button_group) + button_group.addItem('', True, base_menu.MenuCmd(parent, 'onSetting')) + button_group.addItem('', True, base_menu.MenuCmd(parent, 'onClose')) button_group_wrapper.setWidget(button_group) self.add(button_group_wrapper) self.addStyleName('widgetHeader') @@ -206,7 +213,7 @@ self.__title = Label(title) self.__title.setStyleName('widgetHeader_title') self._close_listeners = [] - header = WidgetHeader(self, self.__title) + header = WidgetHeader(self, host, self.__title) self.add(header) self.setSize('100%', '100%') self.addStyleName('widget') @@ -399,9 +406,20 @@ VerticalPanel.doAttachChildren(self) def matchEntity(self, entity): - """This method should be overwritten by child classes.""" + """Check if this widget corresponds to the given entity. + + This method should be overwritten by child classes. + @return: True if the widget matches the entity""" raise NotImplementedError + def addCachedMenus(self, menu_bar): + """Add cached menus to the header. + + This method can be overwritten by child classes. + @param menu_bar (GenericMenuBar): menu bar of the widget's header + """ + pass + class ScrollPanelWrapper(SimplePanel): """Scroll Panel like component, wich use the full available space diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/sat_browser/file_tools.py --- a/src/browser/sat_browser/file_tools.py Tue Jul 15 13:39:36 2014 +0200 +++ b/src/browser/sat_browser/file_tools.py Tue Jul 15 18:43:55 2014 +0200 @@ -151,3 +151,13 @@ else: Window.alert(_('Submit error: %s' % result)) self.upload_btn.setEnabled(True) + + +class AvatarUpload(FileUploadPanel): + def __init__(self): + texts = {'ok_button': 'Upload avatar', + 'body': 'Please select an image to show as your avatar...
Your picture must be a square and will be resized to 64x64 pixels if necessary.', + 'errback': "Can't open image... did you actually submit an image?", + 'body_errback': 'Please select another image file.', + 'callback': "Your new profile picture has been set!"} + FileUploadPanel.__init__(self, 'upload_avatar', 'avatar_path', 2, texts) diff -r 636b6c477a87 -r 5d8632a7bfde src/browser/sat_browser/menu.py --- a/src/browser/sat_browser/menu.py Tue Jul 15 13:39:36 2014 +0200 +++ b/src/browser/sat_browser/menu.py Tue Jul 15 18:43:55 2014 +0200 @@ -24,142 +24,78 @@ from sat.core.i18n import _ from pyjamas.ui.SimplePanel import SimplePanel -from pyjamas.ui.MenuBar import MenuBar -from pyjamas.ui.MenuItem import MenuItem from pyjamas.ui.HTML import HTML from pyjamas.ui.Frame import Frame from pyjamas import Window +from constants import Const as C import jid - import file_tools import xmlui import panels import dialog import contact_group - - -class MenuCmd: - - def __init__(self, object_, handler): - self._object = object_ - self._handler = handler - - def execute(self): - handler = getattr(self._object, self._handler) - handler() - - -class PluginMenuCmd: - - def __init__(self, host, action_id): - self.host = host - self.action_id = action_id - - def execute(self): - self.host.launchAction(self.action_id, None) +import base_menu +from base_menu import MenuCmd -class LiberviaMenuBar(MenuBar): +class MainMenuBar(base_menu.GenericMenuBar): + """The main menu bar which is displayed on top of the document""" - def __init__(self): - MenuBar.__init__(self, vertical=False) + ITEM_TPL = "%s" - def doItemAction(self, item, fireCommand): - MenuBar.doItemAction(self, item, fireCommand) - if item == self.items[-1] and self.popup: - self.popup.setPopupPosition(Window.getClientWidth() - - self.popup.getOffsetWidth() - 22, - self.getAbsoluteTop() + - self.getOffsetHeight() - 1) - self.popup.addStyleName('menuLastPopup') + def __init__(self, host): + base_menu.GenericMenuBar.__init__(self, host, vertical=False) + self.moved_popup_style = 'menuLastPopup' + + @classmethod + def getCategoryHTML(cls, type_, menu_name_i18n): + return cls.ITEM_TPL % (type_, menu_name_i18n) -class AvatarUpload(file_tools.FileUploadPanel): - def __init__(self): - texts = {'ok_button': 'Upload avatar', - 'body': 'Please select an image to show as your avatar...
Your picture must be a square and will be resized to 64x64 pixels if necessary.', - 'errback': "Can't open image... did you actually submit an image?", - 'body_errback': 'Please select another image file.', - 'callback': "Your new profile picture has been set!"} - file_tools.FileUploadPanel.__init__(self, 'upload_avatar', 'avatar_path', 2, texts) - - -class Menu(SimplePanel): +class MainMenuPanel(SimplePanel): + """Container for the main menu bar""" def __init__(self, host): self.host = host SimplePanel.__init__(self) self.setStyleName('menuContainer') + self.menu_bar = MainMenuBar(self.host) - def createMenus(self, add_menus): - _item_tpl = "%s" - menus_dict = {} - menus_order = [] + def addMenu(self, *args): + self.menu_bar.addMenu(*args) + + def addCachedMenus(self, *args): + self.menu_bar.addCachedMenus(*args) - def addMenu(menu_name, menu_name_i18n, item_name_i18n, icon, menu_cmd): - """ add a menu to menu_dict """ - log.info("addMenu: %s %s %s %s %s" % (menu_name, menu_name_i18n, item_name_i18n, icon, menu_cmd)) - try: - menu_bar = menus_dict[menu_name] - except KeyError: - menu_bar = menus_dict[menu_name] = MenuBar(vertical=True) - menus_order.append((menu_name, menu_name_i18n, icon)) - if item_name_i18n and menu_cmd: - menu_bar.addItem(item_name_i18n, menu_cmd) - - addMenu("General", _("General"), _("Web widget"), 'home', MenuCmd(self, "onWebWidget")) - addMenu("General", _("General"), _("Disconnect"), 'home', MenuCmd(self, "onDisconnect")) - addMenu("Contacts", _("Contacts"), None, 'social', None) - addMenu("Groups", _("Groups"), _("Discussion"), 'social', MenuCmd(self, "onJoinRoom")) - addMenu("Groups", _("Groups"), _("Collective radio"), 'social', MenuCmd(self, "onCollectiveRadio")) - addMenu("Games", _("Games"), _("Tarot"), 'games', MenuCmd(self, "onTarotGame")) - addMenu("Games", _("Games"), _("Xiangqi"), 'games', MenuCmd(self, "onXiangqiGame")) + def createMenus(self): + self.addMenu("General", _("General"), _("Web widget"), 'home', MenuCmd(self, "onWebWidget")) + self.addMenu("General", _("General"), _("Disconnect"), 'home', MenuCmd(self, "onDisconnect")) + self.addMenu("Contacts", _("Contacts"), None, 'social', None) + self.addMenu("Groups", _("Groups"), _("Discussion"), 'social', MenuCmd(self, "onJoinRoom")) + self.addMenu("Groups", _("Groups"), _("Collective radio"), 'social', MenuCmd(self, "onCollectiveRadio")) + self.addMenu("Games", _("Games"), _("Tarot"), 'games', MenuCmd(self, "onTarotGame")) + self.addMenu("Games", _("Games"), _("Xiangqi"), 'games', MenuCmd(self, "onXiangqiGame")) # additional menus - for action_id, type_, path, path_i18n in add_menus: - if not path: - log.warning("skipping menu without path") - continue - if len(path) != len(path_i18n): - log.error("inconsistency between menu paths") - continue - menu_name = path[0] - menu_name_i18n = path_i18n[0] - item_name = path[1:] - if not item_name: - log.warning("skipping menu with a path of lenght 1 [%s]" % path[0]) - continue - item_name_i18n = ' | '.join(path_i18n[1:]) - addMenu(menu_name, menu_name_i18n, item_name_i18n, 'plugins', PluginMenuCmd(self.host, action_id)) + self.addCachedMenus(C.MENU_GLOBAL) # menu items that should be displayed after the automatically added ones - addMenu("Contacts", _("Contacts"), _("Manage groups"), 'social', MenuCmd(self, "onManageContactGroups")) + self.addMenu("Contacts", _("Contacts"), _("Manage groups"), 'social', MenuCmd(self, "onManageContactGroups")) - menus_order.append(None) # we add separator + self.menu_bar.addSeparator() - addMenu("Help", _("Help"), _("Social contract"), 'help', MenuCmd(self, "onSocialContract")) - addMenu("Help", _("Help"), _("About"), 'help', MenuCmd(self, "onAbout")) - addMenu("Settings", _("Settings"), _("Account"), 'settings', MenuCmd(self, "onAccount")) - addMenu("Settings", _("Settings"), _("Parameters"), 'settings', MenuCmd(self, "onParameters")) + self.addMenu("Help", _("Help"), _("Social contract"), 'help', MenuCmd(self, "onSocialContract")) + self.addMenu("Help", _("Help"), _("About"), 'help', MenuCmd(self, "onAbout")) + self.addMenu("Settings", _("Settings"), _("Account"), 'settings', MenuCmd(self, "onAccount")) + self.addMenu("Settings", _("Settings"), _("Parameters"), 'settings', MenuCmd(self, "onParameters")) # XXX: temporary, will change when a full profile will be managed in SàT - addMenu("Settings", _("Settings"), _("Upload avatar"), 'settings', MenuCmd(self, "onAvatarUpload")) - - menubar = LiberviaMenuBar() + self.addMenu("Settings", _("Settings"), _("Upload avatar"), 'settings', MenuCmd(self, "onAvatarUpload")) - for menu_data in menus_order: - if menu_data is None: - _separator = MenuItem('', None) - _separator.setStyleName('menuSeparator') - menubar.addItem(_separator, None) - else: - menu_name, menu_name_i18n, icon = menu_data - menubar.addItem(MenuItem(_item_tpl % (icon, menu_name_i18n), True, menus_dict[menu_name])) + self.add(self.menu_bar) - self.add(menubar) - - #General menu + # General menu def onWebWidget(self): web_panel = panels.WebPanel(self.host, "http://www.goffi.org") self.host.addWidget(web_panel) @@ -262,7 +198,7 @@ self.menu_settings.removeItem(self.item_params) def onAvatarUpload(self): - body = AvatarUpload() + body = file_tools.AvatarUpload() _dialog = dialog.GenericDialog("Avatar upload", body, options=['NO_CLOSE']) body.setCloseCb(_dialog.close) _dialog.setWidth('40%')