comparison src/browser/sat_browser/base_panel.py @ 735:e4ae8e2b0afd

browser_side: improve PopupMenuPanel comments + rename a button + better PEP8 compliance
author souliane <souliane@mailoo.org>
date Thu, 19 Nov 2015 11:19:05 +0100
parents 9877607c719a
children 4545d48dee60
comparison
equal deleted inserted replaced
734:26046f13e93b 735:e4ae8e2b0afd
35 35
36 ### Menus ### 36 ### Menus ###
37 37
38 38
39 class PopupMenuPanel(PopupPanel): 39 class PopupMenuPanel(PopupPanel):
40 """This implementation of a popup menu (context menu) allow you to assign 40 """Popup menu (contextual menu) with common callbacks for all the items.
41 two special methods which are common to all the items, in order to hide 41
42 certain items and also easily define their callbacks. The menu can be 42 This implementation of a popup menu allow you to assign two special methods which
43 bound to any of the mouse button (left, middle, right). 43 are common to all the items, in order to hide certain items and define their callbacks.
44 callbacks. The menu can be bound to any button of the mouse (left, middle, right).
44 """ 45 """
46
45 def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs): 47 def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs):
46 """ 48 """
47 @param entries: a dict of dicts, where each sub-dict is representing 49 @param entries (dict{unicode: dict{unicode: unicode}:
48 one menu item: the sub-dict key can be used as the item text and 50 - menu item keys
49 description, but optional "title" and "desc" entries would be used 51 - values: dict{unicode: unicode}:
50 if they exists. The sub-dicts may be extended later to do 52 - item data lile "title", "desc"...
51 more complicated stuff or overwrite the common methods. 53 - value
52 @param hide: function with 2 args: widget, key as string and 54 @param hide (callable): function of signature Widget, unicode: bool
53 returns True if that item should be hidden from the context menu. 55 which takes the sender and the item key, and returns True if that
54 @param callback: function with 2 args: sender, key as string 56 item has to be hidden from the context menu.
55 @param vertical: True or False, to set the direction 57 @param callback (callbable): function of signature Widget, unicode: None
56 @param item_style: alternative CSS class for the menu items 58 which takes the sender and the item key.
57 @param menu_style: supplementary CSS class for the sender widget 59 @param vertical (bool): set the direction vertical or horizontal
60 @param item_style (unicode): alternative CSS class for the menu items
61 @param menu_style (unicode): supplementary CSS class for the sender widget
58 """ 62 """
59 PopupPanel.__init__(self, autoHide=True, **kwargs) 63 PopupPanel.__init__(self, autoHide=True, **kwargs)
60 self._entries = entries 64 self.entries = entries
61 self._hide = hide 65 self.hideMenu = hide
62 self._callback = callback 66 self.callback = callback
63 self.vertical = vertical 67 self.vertical = vertical
64 self.style = {"selected": None, "menu": "itemKeyMenu", "item": "popupMenuItem"} 68 self.style = {"selected": None, "menu": "itemKeyMenu", "item": "popupMenuItem"}
65 if isinstance(style, dict): 69 if isinstance(style, dict):
66 self.style.update(style) 70 self.style.update(style)
67 self._senders = {} 71 self.senders = {}
68 72
69 def _show(self, sender): 73 def showMenu(self, sender):
70 """Popup the menu relative to this sender's position. 74 """Popup the menu on the screen, where it fits to the sender's position.
71 @param sender: the widget that has been clicked 75
76 @param sender (Widget): the widget that has been clicked
72 """ 77 """
73 menu = VerticalPanel() if self.vertical is True else HorizontalPanel() 78 menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
74 menu.setStyleName(self.style["menu"]) 79 menu.setStyleName(self.style["menu"])
75 80
76 def button_cb(item): 81 def button_cb(item):
77 """You can not put that method in the loop and rely 82 # XXX: you can not put that method in the loop and rely on key
78 on _key, because it is overwritten by each step. 83 if self.callback is not None:
79 You can rely on item.key instead, which is copied 84 self.callback(sender=sender, key=item.key)
80 from _key after the item creation.
81 @param item: the menu item that has been clicked
82 """
83 if self._callback is not None:
84 self._callback(sender=sender, key=item.key)
85 self.hide(autoClosed=True) 85 self.hide(autoClosed=True)
86 86
87 for _key in self._entries.keys(): 87 for key, entry in self.entries.iteritems():
88 entry = self._entries[_key] 88 if self.hideMenu is not None and self.hideMenu(sender=sender, key=key) is True:
89 if self._hide is not None and self._hide(sender=sender, key=_key) is True:
90 continue 89 continue
91 title = entry["title"] if "title" in entry.keys() else _key 90 title = entry.get("title", key)
92 item = Button(title, button_cb) 91 item = Button(title, button_cb, StyleName=self.style["item"])
93 item.key = _key 92 item.key = key # XXX: copy the key because we loop on it and it will change
94 item.setStyleName(self.style["item"]) 93 item.setTitle(entry.get("desc", title))
95 item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
96 menu.add(item) 94 menu.add(item)
97 if len(menu.getChildren()) == 0: 95
98 return 96 if menu.getWidgetCount() == 0:
97 return # no item to display means no menu at all
98
99 self.add(menu) 99 self.add(menu)
100
100 if self.vertical is True: 101 if self.vertical is True:
101 x = sender.getAbsoluteLeft() + sender.getOffsetWidth() 102 x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
102 y = sender.getAbsoluteTop() 103 y = sender.getAbsoluteTop()
103 else: 104 else:
104 x = sender.getAbsoluteLeft() 105 x = sender.getAbsoluteLeft()
105 y = sender.getAbsoluteTop() + sender.getOffsetHeight() 106 y = sender.getAbsoluteTop() + sender.getOffsetHeight()
107
106 self.setPopupPosition(x, y) 108 self.setPopupPosition(x, y)
107 self.show() 109 self.show()
110
108 if self.style["selected"]: 111 if self.style["selected"]:
109 sender.addStyleDependentName(self.style["selected"]) 112 sender.addStyleDependentName(self.style["selected"])
110 113
111 def _onHide(popup): 114 def onHide(popup):
112 if self.style["selected"]: 115 if self.style["selected"]:
113 sender.removeStyleDependentName(self.style["selected"]) 116 sender.removeStyleDependentName(self.style["selected"])
114 return PopupPanel.onHideImpl(self, popup) 117 return PopupPanel.onHideImpl(self, popup)
115 118
116 self.onHideImpl = _onHide 119 self.onHideImpl = onHide
117 120
118 def registerClickSender(self, sender, button=BUTTON_LEFT): 121 def registerClickSender(self, sender, button=BUTTON_LEFT):
119 """Bind the menu to the specified sender. 122 """Bind the menu to the specified sender.
120 @param sender: the widget to which the menu should be bound 123
121 @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT 124 @param sender (Widget): bind the menu to this widget
122 """ 125 @param (int): BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
123 self._senders.setdefault(sender, []) 126 """
124 self._senders[sender].append(button) 127 self.senders.setdefault(sender, [])
128 self.senders[sender].append(button)
125 129
126 if button == BUTTON_RIGHT: 130 if button == BUTTON_RIGHT:
127 # WARNING: to disable the context menu is a bit tricky... 131 # WARNING: to disable the context menu is a bit tricky...
128 # The following seems to work on Firefox 24.0, but: 132 # The following seems to work on Firefox 24.0, but:
129 # TODO: find a cleaner way to disable the context menu 133 # TODO: find a cleaner way to disable the context menu
130 sender.getElement().setAttribute("oncontextmenu", "return false") 134 sender.getElement().setAttribute("oncontextmenu", "return false")
131 135
132 def _onBrowserEvent(event): 136 def onBrowserEvent(event):
133 button = DOM.eventGetButton(event) 137 button = DOM.eventGetButton(event)
134 if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]: 138 if DOM.eventGetType(event) == "mousedown" and button in self.senders[sender]:
135 self._show(sender) 139 self.showMenu(sender)
136 return sender.__class__.onBrowserEvent(sender, event) 140 return sender.__class__.onBrowserEvent(sender, event)
137 141
138 sender.onBrowserEvent = _onBrowserEvent 142 sender.onBrowserEvent = onBrowserEvent
139 143
140 def registerMiddleClickSender(self, sender): 144 def registerMiddleClickSender(self, sender):
141 self.registerClickSender(sender, BUTTON_MIDDLE) 145 self.registerClickSender(sender, BUTTON_MIDDLE)
142 146
143 def registerRightClickSender(self, sender): 147 def registerRightClickSender(self, sender):