diff browser_side/panels.py @ 241:86055ccf69c3

browser_side: added class PopupMenuPanel to manage more complex context menu
author souliane <souliane@mailoo.org>
date Tue, 15 Oct 2013 13:36:51 +0200
parents b911f2b43fd4
children a25aa882e09a
line wrap: on
line diff
--- a/browser_side/panels.py	Tue Oct 15 13:39:21 2013 +0200
+++ b/browser_side/panels.py	Tue Oct 15 13:36:51 2013 +0200
@@ -32,8 +32,10 @@
 from pyjamas.ui.Button import Button
 from pyjamas.ui.HTML import HTML
 from pyjamas.ui.Image import Image
+from pyjamas.ui.PopupPanel import PopupPanel
 from pyjamas.ui.ClickListener import ClickHandler
 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN
+from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
 from pyjamas.ui.MouseListener import MouseHandler
 from pyjamas.Timer import Timer
 from pyjamas import DOM
@@ -546,9 +548,9 @@
             entry.addStyleName('selected_entry')
         self.selected_entry = entry
 
-    def updateValue(self, type, jid, value):
+    def updateValue(self, type_, jid, value):
         """Update a jid value in entries
-        @param type: one of 'avatar', 'nick'
+        @param type_: one of 'avatar', 'nick'
         @param jid: jid concerned
         @param value: new value"""
         def updateVPanel(vpanel):
@@ -557,7 +559,7 @@
                     child.updateAvatar(value)
                 elif isinstance(child, VerticalPanel):
                     updateVPanel(child)
-        if type == 'avatar':
+        if type_ == 'avatar':
             updateVPanel(self.vpanel)
 
     def setAcceptedGroup(self, group):
@@ -940,3 +942,108 @@
             tab_bar_h = _elts.item(0).offsetHeight
         ideal_height = Window.getClientHeight() - tab_bar_h
         self.setHeight("%s%s" % (ideal_height, "px"))
+
+
+class PopupMenuPanel(PopupPanel):
+    """This implementation of a popup menu (context menu) allow you to assign
+    two special methods which are common to all the items, in order to hide
+    certain items and also easily define their callbacks. The menu can be
+    bound to any of the mouse button (left, middle, right).
+    """
+    def __init__(self, entries, hide=None, callback=None, vertical=True, item_style="popupMenuItem", menu_style=None, **kwargs):
+        """
+        @param entries: a dict of dicts, where each sub-dict is representing
+        one menu item: the sub-dict key can be used as the item text and
+        description, but optional "title" and "desc" entries would be used
+        if they exists. The sub-dicts may be extended later to do
+        more complicated stuff or overwrite the common methods.
+        @param hide: function  with 2 args: widget, key as string and
+        returns True if that item should be hidden from the context menu.
+        @param callback: function with 2 args: widget, key as string
+        @param vertical: True or False, to set the direction
+        @param item_style: alternative CSS class for the menu items
+        @param menu_style: supplementary CSS class for the sender widget
+        """
+        PopupPanel.__init__(self, autoHide=True, **kwargs)
+        self._entries = entries
+        self._hide = hide
+        self._callback = callback
+        self.vertical = vertical
+        self.item_style = item_style
+        self.menu_style = menu_style
+        self._senders = {}
+
+    def _show(self, sender):
+        """Popup the menu relative to this sender's position.
+        @param sender: the widget that has been clicked
+        """
+        menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
+        menu.setStyleName("recipientTypeMenu")
+
+        def button_cb(item):
+            """You can not put that method in the loop and rely
+            on _key, because it is overwritten by each step.
+            You can rely on item.key instead, which is copied
+            from _key after the item creation.
+            @param item: the menu item that has been clicked
+            """
+            if self._callback is not None:
+                self._callback(sender=sender, key=item.key)
+            self.hide(autoClosed=True)
+
+        for _key in self._entries.keys():
+            entry = self._entries[_key]
+            if self._hide is not None and self._hide(sender=sender, key=_key) is True:
+                continue
+            title = entry["title"] if "title" in entry.keys() else _key
+            item = Button(title, button_cb)
+            item.key = _key
+            item.setStyleName(self.item_style)
+            item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
+            menu.add(item)
+        self.add(menu)
+        if self.vertical is True:
+            x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
+            y = sender.getAbsoluteTop()
+        else:
+            x = sender.getAbsoluteLeft()
+            y = sender.getAbsoluteTop() + sender.getOffsetHeight()
+        self.setPopupPosition(x, y)
+        self.show()
+        if self.menu_style:
+            sender.addStyleDependentName(self.menu_style)
+
+        def _onHide(popup):
+            if hasattr(self, "menu_style") and self.menu_style is not None:
+                sender.removeStyleDependentName(self.menu_style)
+            return PopupPanel.onHideImpl(self, popup)
+
+        self.onHideImpl = _onHide
+
+    def registerClickSender(self, sender, button=BUTTON_LEFT):
+        """Bind the menu to the specified sender.
+        @param sender: the widget to which the menu should be bound
+        @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
+        """
+        self._senders.setdefault(sender, [])
+        self._senders[sender].append(button)
+
+        if button == BUTTON_RIGHT:
+            # WARNING: to disable the context menu is a bit tricky...
+            # The following seems to work on Firefox 24.0, but:
+            # TODO: find a cleaner way to disable the context menu
+            sender.getElement().setAttribute("oncontextmenu", "return false")
+
+        def _onBrowserEvent(event):
+            button = DOM.eventGetButton(event)
+            if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]:
+                self._show(sender)
+            return sender.__class__.onBrowserEvent(sender, event)
+
+        sender.onBrowserEvent = _onBrowserEvent
+
+    def registerMiddleClickSender(self, sender):
+        self.registerClickSender(sender, BUTTON_MIDDLE)
+
+    def registerRightClickSender(self, sender):
+        self.registerClickSender(sender, BUTTON_RIGHT)