comparison src/browser/sat_browser/base_panel.py @ 648:6d3142b782c3 frontends_multi_profiles

browser_side: classes reorganisation: - moved widgets in dedicated modules (base, contact, editor, libervia) and a widget module for single classes - same thing for panels (base, main, contact) - libervia_widget mix main panels and widget and drag n drop for technical reasons (see comments) - renamed WebPanel to WebWidget
author Goffi <goffi@goffi.org>
date Thu, 26 Feb 2015 18:10:54 +0100
parents src/browser/sat_browser/base_panels.py@e0021d571eef
children 9877607c719a
comparison
equal deleted inserted replaced
647:e0021d571eef 648:6d3142b782c3
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011, 2012, 2013, 2014 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 from sat.core.log import getLogger
21 log = getLogger(__name__)
22 from sat.core.i18n import _
23
24 from pyjamas.ui.VerticalPanel import VerticalPanel
25 from pyjamas.ui.HorizontalPanel import HorizontalPanel
26 from pyjamas.ui.ScrollPanel import ScrollPanel
27 from pyjamas.ui.Button import Button
28 from pyjamas.ui.SimplePanel import SimplePanel
29 from pyjamas.ui.PopupPanel import PopupPanel
30 from pyjamas.ui.StackPanel import StackPanel
31 from pyjamas.ui.TextArea import TextArea
32 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
33 from pyjamas import DOM
34
35
36 ### Menus ###
37
38
39 class PopupMenuPanel(PopupPanel):
40 """This implementation of a popup menu (context menu) allow you to assign
41 two special methods which are common to all the items, in order to hide
42 certain items and also easily define their callbacks. The menu can be
43 bound to any of the mouse button (left, middle, right).
44 """
45 def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs):
46 """
47 @param entries: a dict of dicts, where each sub-dict is representing
48 one menu item: the sub-dict key can be used as the item text and
49 description, but optional "title" and "desc" entries would be used
50 if they exists. The sub-dicts may be extended later to do
51 more complicated stuff or overwrite the common methods.
52 @param hide: function with 2 args: widget, key as string and
53 returns True if that item should be hidden from the context menu.
54 @param callback: function with 2 args: sender, key as string
55 @param vertical: True or False, to set the direction
56 @param item_style: alternative CSS class for the menu items
57 @param menu_style: supplementary CSS class for the sender widget
58 """
59 PopupPanel.__init__(self, autoHide=True, **kwargs)
60 self._entries = entries
61 self._hide = hide
62 self._callback = callback
63 self.vertical = vertical
64 self.style = {"selected": None, "menu": "itemKeyMenu", "item": "popupMenuItem"}
65 if isinstance(style, dict):
66 self.style.update(style)
67 self._senders = {}
68
69 def _show(self, sender):
70 """Popup the menu relative to this sender's position.
71 @param sender: the widget that has been clicked
72 """
73 menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
74 menu.setStyleName(self.style["menu"])
75
76 def button_cb(item):
77 """You can not put that method in the loop and rely
78 on _key, because it is overwritten by each step.
79 You can rely on item.key instead, which is copied
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)
86
87 for _key in self._entries.keys():
88 entry = self._entries[_key]
89 if self._hide is not None and self._hide(sender=sender, key=_key) is True:
90 continue
91 title = entry["title"] if "title" in entry.keys() else _key
92 item = Button(title, button_cb)
93 item.key = _key
94 item.setStyleName(self.style["item"])
95 item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
96 menu.add(item)
97 if len(menu.getChildren()) == 0:
98 return
99 self.add(menu)
100 if self.vertical is True:
101 x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
102 y = sender.getAbsoluteTop()
103 else:
104 x = sender.getAbsoluteLeft()
105 y = sender.getAbsoluteTop() + sender.getOffsetHeight()
106 self.setPopupPosition(x, y)
107 self.show()
108 if self.style["selected"]:
109 sender.addStyleDependentName(self.style["selected"])
110
111 def _onHide(popup):
112 if self.style["selected"]:
113 sender.removeStyleDependentName(self.style["selected"])
114 return PopupPanel.onHideImpl(self, popup)
115
116 self.onHideImpl = _onHide
117
118 def registerClickSender(self, sender, button=BUTTON_LEFT):
119 """Bind the menu to the specified sender.
120 @param sender: the widget to which the menu should be bound
121 @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
122 """
123 self._senders.setdefault(sender, [])
124 self._senders[sender].append(button)
125
126 if button == BUTTON_RIGHT:
127 # WARNING: to disable the context menu is a bit tricky...
128 # The following seems to work on Firefox 24.0, but:
129 # TODO: find a cleaner way to disable the context menu
130 sender.getElement().setAttribute("oncontextmenu", "return false")
131
132 def _onBrowserEvent(event):
133 button = DOM.eventGetButton(event)
134 if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]:
135 self._show(sender)
136 return sender.__class__.onBrowserEvent(sender, event)
137
138 sender.onBrowserEvent = _onBrowserEvent
139
140 def registerMiddleClickSender(self, sender):
141 self.registerClickSender(sender, BUTTON_MIDDLE)
142
143 def registerRightClickSender(self, sender):
144 self.registerClickSender(sender, BUTTON_RIGHT)
145
146
147 ### Generic panels ###
148
149
150 class ToggleStackPanel(StackPanel):
151 """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be
152 visible at the same time, clicking a sub-panel header will not display it and hide
153 the others but only toggle its own visibility. The argument 'visibleStack' is ignored.
154 Note that the argument 'visible' has been added to listener's 'onStackChanged' method.
155 """
156
157 def __init__(self, **kwargs):
158 StackPanel.__init__(self, **kwargs)
159
160 def onBrowserEvent(self, event):
161 if DOM.eventGetType(event) == "click":
162 index = self.getDividerIndex(DOM.eventGetTarget(event))
163 if index != -1:
164 self.toggleStack(index)
165
166 def add(self, widget, stackText="", asHTML=False, visible=False):
167 StackPanel.add(self, widget, stackText, asHTML)
168 self.setStackVisible(self.getWidgetCount() - 1, visible)
169
170 def toggleStack(self, index):
171 if index >= self.getWidgetCount():
172 return
173 visible = not self.getWidget(index).getVisible()
174 self.setStackVisible(index, visible)
175 for listener in self.stackListeners:
176 listener.onStackChanged(self, index, visible)
177
178
179 class TitlePanel(ToggleStackPanel):
180 """A toggle panel to set the message title"""
181 def __init__(self):
182 ToggleStackPanel.__init__(self, Width="100%")
183 self.text_area = TextArea()
184 self.add(self.text_area, _("Title"))
185 self.addStackChangeListener(self)
186
187 def onStackChanged(self, sender, index, visible=None):
188 if visible is None:
189 visible = sender.getWidget(index).getVisible()
190 text = self.text_area.getText()
191 suffix = "" if (visible or not text) else (": %s" % text)
192 sender.setStackText(index, _("Title") + suffix)
193
194 def getText(self):
195 return self.text_area.getText()
196
197 def setText(self, text):
198 self.text_area.setText(text)
199
200
201 class ScrollPanelWrapper(SimplePanel):
202 """Scroll Panel like component, wich use the full available space
203 to work around percent size issue, it use some of the ideas found
204 here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316
205 specially in code given at comment #46, thanks to Stefan Bachert"""
206
207 def __init__(self, *args, **kwargs):
208 SimplePanel.__init__(self)
209 self.spanel = ScrollPanel(*args, **kwargs)
210 SimplePanel.setWidget(self, self.spanel)
211 DOM.setStyleAttribute(self.getElement(), "position", "relative")
212 DOM.setStyleAttribute(self.getElement(), "top", "0px")
213 DOM.setStyleAttribute(self.getElement(), "left", "0px")
214 DOM.setStyleAttribute(self.getElement(), "width", "100%")
215 DOM.setStyleAttribute(self.getElement(), "height", "100%")
216 DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute")
217 DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%")
218 DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%")
219
220 def setWidget(self, widget):
221 self.spanel.setWidget(widget)
222
223 def setScrollPosition(self, position):
224 self.spanel.setScrollPosition(position)
225
226 def scrollToBottom(self):
227 self.setScrollPosition(self.spanel.getElement().scrollHeight)