comparison browser_side/base_panels.py @ 323:0b7934e75e76

misc: reorganization of the file panels.py + clean the modules import: - some existing modules were not found during JS runtime (panels.py was too large?) - the *Panel classes of panels.py that do not reference "host" have been moved to base_panels.py - cleaned the import in various files
author souliane <souliane@mailoo.org>
date Sat, 04 Jan 2014 00:17:46 +0100
parents browser_side/panels.py@971e3812903a
children 2067d6241927
comparison
equal deleted inserted replaced
322:971e3812903a 323:0b7934e75e76
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 Libervia: a Salut à Toi frontend
6 Copyright (C) 2011, 2012, 2013 Jérôme Poisson <goffi@goffi.org>
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Affero General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22 import pyjd # this is dummy in pyjs
23 from pyjamas.ui.AbsolutePanel import AbsolutePanel
24 from pyjamas.ui.VerticalPanel import VerticalPanel
25 from pyjamas.ui.HorizontalPanel import HorizontalPanel
26 from pyjamas.ui.HTMLPanel import HTMLPanel
27 from pyjamas.ui.Button import Button
28 from pyjamas.ui.HTML import HTML
29 from pyjamas.ui.PopupPanel import PopupPanel
30 from pyjamas.ui.StackPanel import StackPanel
31 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
32 from pyjamas import DOM
33
34 from datetime import datetime
35 from time import time
36
37 from tools import html_sanitize, inlineRoot
38
39 from sat_frontends.tools.strings import addURLToText
40
41
42 class ChatText(HTMLPanel):
43
44 def __init__(self, timestamp, nick, mymess, msg, xhtml=None):
45 _date = datetime.fromtimestamp(float(timestamp or time()))
46 _msg_class = ["chat_text_msg"]
47 if mymess:
48 _msg_class.append("chat_text_mymess")
49 HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" %
50 {"timestamp": _date.strftime("%H:%M"),
51 "nick": "[%s]" % html_sanitize(nick),
52 "msg_class": ' '.join(_msg_class),
53 "msg": addURLToText(html_sanitize(msg)) if not xhtml else inlineRoot(xhtml)} # FIXME: images and external links must be removed according to preferences
54 )
55 self.setStyleName('chatText')
56
57
58 class Occupant(HTML):
59 """Occupant of a MUC room"""
60
61 def __init__(self, nick, special=""):
62 HTML.__init__(self)
63 self.nick = nick
64 self.special = special
65 self._refresh()
66
67 def __str__(self):
68 return self.nick
69
70 def addSpecial(self, special=""):
71 if special not in self.special:
72 self.special += special
73 self._refresh()
74
75 def _refresh(self):
76 special = "" if len(self.special) == 0 else " %s" % self.special
77 self.setHTML("<div class='occupant'>%s%s</div>" % (html_sanitize(self.nick), special))
78
79
80 class OccupantsList(AbsolutePanel):
81 """Panel user to show occupants of a room"""
82
83 def __init__(self):
84 AbsolutePanel.__init__(self)
85 self.occupants_list = {}
86 self.setStyleName('occupantsList')
87
88 def addOccupant(self, nick):
89 _occupant = Occupant(nick)
90 self.occupants_list[nick] = _occupant
91 self.add(_occupant)
92
93 def removeOccupant(self, nick):
94 try:
95 self.remove(self.occupants_list[nick])
96 except KeyError:
97 print "ERROR: trying to remove an unexisting nick"
98
99 def clear(self):
100 self.occupants_list.clear()
101 AbsolutePanel.clear(self)
102
103 def addSpecials(self, occupants=[], html=""):
104 index = 0
105 special = html
106 for occupant in occupants:
107 if occupant in self.occupants_list.keys():
108 if isinstance(html, list):
109 special = html[index]
110 index = (index + 1) % len(html)
111 self.occupants_list[occupant].addSpecial(special)
112
113
114 class PopupMenuPanel(PopupPanel):
115 """This implementation of a popup menu (context menu) allow you to assign
116 two special methods which are common to all the items, in order to hide
117 certain items and also easily define their callbacks. The menu can be
118 bound to any of the mouse button (left, middle, right).
119 """
120 def __init__(self, entries, hide=None, callback=None, vertical=True, style={}, **kwargs):
121 """
122 @param entries: a dict of dicts, where each sub-dict is representing
123 one menu item: the sub-dict key can be used as the item text and
124 description, but optional "title" and "desc" entries would be used
125 if they exists. The sub-dicts may be extended later to do
126 more complicated stuff or overwrite the common methods.
127 @param hide: function with 2 args: widget, key as string and
128 returns True if that item should be hidden from the context menu.
129 @param callback: function with 2 args: sender, key as string
130 @param vertical: True or False, to set the direction
131 @param item_style: alternative CSS class for the menu items
132 @param menu_style: supplementary CSS class for the sender widget
133 """
134 PopupPanel.__init__(self, autoHide=True, **kwargs)
135 self._entries = entries
136 self._hide = hide
137 self._callback = callback
138 self.vertical = vertical
139 self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"}
140 self.style.update(style)
141 self._senders = {}
142
143 def _show(self, sender):
144 """Popup the menu relative to this sender's position.
145 @param sender: the widget that has been clicked
146 """
147 menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
148 menu.setStyleName(self.style["menu"])
149
150 def button_cb(item):
151 """You can not put that method in the loop and rely
152 on _key, because it is overwritten by each step.
153 You can rely on item.key instead, which is copied
154 from _key after the item creation.
155 @param item: the menu item that has been clicked
156 """
157 if self._callback is not None:
158 self._callback(sender=sender, key=item.key)
159 self.hide(autoClosed=True)
160
161 for _key in self._entries.keys():
162 entry = self._entries[_key]
163 if self._hide is not None and self._hide(sender=sender, key=_key) is True:
164 continue
165 title = entry["title"] if "title" in entry.keys() else _key
166 item = Button(title, button_cb)
167 item.key = _key
168 item.setStyleName(self.style["item"])
169 item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
170 menu.add(item)
171 if len(menu.getChildren()) == 0:
172 return
173 self.add(menu)
174 if self.vertical is True:
175 x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
176 y = sender.getAbsoluteTop()
177 else:
178 x = sender.getAbsoluteLeft()
179 y = sender.getAbsoluteTop() + sender.getOffsetHeight()
180 self.setPopupPosition(x, y)
181 self.show()
182 if self.style["selected"]:
183 sender.addStyleDependentName(self.style["selected"])
184
185 def _onHide(popup):
186 if self.style["selected"]:
187 sender.removeStyleDependentName(self.style["selected"])
188 return PopupPanel.onHideImpl(self, popup)
189
190 self.onHideImpl = _onHide
191
192 def registerClickSender(self, sender, button=BUTTON_LEFT):
193 """Bind the menu to the specified sender.
194 @param sender: the widget to which the menu should be bound
195 @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
196 """
197 self._senders.setdefault(sender, [])
198 self._senders[sender].append(button)
199
200 if button == BUTTON_RIGHT:
201 # WARNING: to disable the context menu is a bit tricky...
202 # The following seems to work on Firefox 24.0, but:
203 # TODO: find a cleaner way to disable the context menu
204 sender.getElement().setAttribute("oncontextmenu", "return false")
205
206 def _onBrowserEvent(event):
207 button = DOM.eventGetButton(event)
208 if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]:
209 self._show(sender)
210 return sender.__class__.onBrowserEvent(sender, event)
211
212 sender.onBrowserEvent = _onBrowserEvent
213
214 def registerMiddleClickSender(self, sender):
215 self.registerClickSender(sender, BUTTON_MIDDLE)
216
217 def registerRightClickSender(self, sender):
218 self.registerClickSender(sender, BUTTON_RIGHT)
219
220
221 class ToggleStackPanel(StackPanel):
222 """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be
223 visible at the same time, clicking a sub-panel header will not display it and hide
224 the others but only toggle its own visibility. The argument 'visibleStack' is ignored.
225 Note that the argument 'visible' has been added to listener's 'onStackChanged' method.
226 """
227
228 def __init__(self, **kwargs):
229 StackPanel.__init__(self, **kwargs)
230
231 def onBrowserEvent(self, event):
232 if DOM.eventGetType(event) == "click":
233 index = self.getDividerIndex(DOM.eventGetTarget(event))
234 if index != -1:
235 self.toggleStack(index)
236
237 def add(self, widget, stackText="", asHTML=False, visible=False):
238 StackPanel.add(self, widget, stackText, asHTML)
239 self.setStackVisible(self.getWidgetCount() - 1, visible)
240
241 def toggleStack(self, index):
242 if index >= self.getWidgetCount():
243 return
244 visible = not self.getWidget(index).getVisible()
245 self.setStackVisible(index, visible)
246 for listener in self.stackListeners:
247 listener.onStackChanged(self, index, visible)