Mercurial > libervia-web
comparison src/browser/sat_browser/contact.py @ 467:97c72fe4a5f2
browser_side: import fixes:
- moved browser modules in a sat_browser packages, to avoid import conflicts with std lib (e.g. logging), and let pyjsbuild work normaly
- refactored bad import practices: classes are most of time not imported directly, module is imported instead.
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 09 Jun 2014 22:15:26 +0200 |
parents | src/browser/contact.py@36f27d1e64b2 |
children | c21ea1fe3593 |
comparison
equal
deleted
inserted
replaced
466:01880aa8ea2d | 467:97c72fe4a5f2 |
---|---|
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 import pyjd # this is dummy in pyjs | |
21 from sat.core.log import getLogger | |
22 log = getLogger(__name__) | |
23 from pyjamas.ui.SimplePanel import SimplePanel | |
24 from pyjamas.ui.ScrollPanel import ScrollPanel | |
25 from pyjamas.ui.VerticalPanel import VerticalPanel | |
26 from pyjamas.ui.ClickListener import ClickHandler | |
27 from pyjamas.ui.Label import Label | |
28 from pyjamas.ui.HTML import HTML | |
29 from pyjamas import Window | |
30 from pyjamas import DOM | |
31 from __pyjamas__ import doc | |
32 | |
33 from jid import JID | |
34 | |
35 import base_panels | |
36 import base_widget | |
37 import panels | |
38 import html_tools | |
39 | |
40 | |
41 def setPresenceStyle(element, presence, base_style="contact"): | |
42 """ | |
43 Set the CSS style of a contact's element according to its presence. | |
44 @param item: the UI element of the contact | |
45 @param presence: a value in ("", "chat", "away", "dnd", "xa"). | |
46 @param base_style: the base name of the style to apply | |
47 """ | |
48 if not hasattr(element, 'presence_style'): | |
49 element.presence_style = None | |
50 style = '%s-%s' % (base_style, presence or 'connected') | |
51 if style == element.presence_style: | |
52 return | |
53 if element.presence_style is not None: | |
54 element.removeStyleName(element.presence_style) | |
55 element.addStyleName(style) | |
56 element.presence_style = style | |
57 | |
58 | |
59 class GroupLabel(base_widget.DragLabel, Label, ClickHandler): | |
60 def __init__(self, host, group): | |
61 self.group = group | |
62 self.host = host | |
63 Label.__init__(self, group) # , Element=DOM.createElement('div') | |
64 self.setStyleName('group') | |
65 base_widget.DragLabel.__init__(self, group, "GROUP") | |
66 ClickHandler.__init__(self) | |
67 self.addClickListener(self) | |
68 | |
69 def onClick(self, sender): | |
70 self.host.getOrCreateLiberviaWidget(panels.MicroblogPanel, self.group) | |
71 | |
72 | |
73 class ContactLabel(base_widget.DragLabel, HTML, ClickHandler): | |
74 def __init__(self, host, jid, name=None, handleClick=True): | |
75 HTML.__init__(self) | |
76 self.host = host | |
77 self.name = name or jid | |
78 self.waiting = False | |
79 self.jid = jid | |
80 self._fill() | |
81 self.setStyleName('contact') | |
82 base_widget.DragLabel.__init__(self, jid, "CONTACT") | |
83 if handleClick: | |
84 ClickHandler.__init__(self) | |
85 self.addClickListener(self) | |
86 | |
87 def _fill(self): | |
88 if self.waiting: | |
89 _wait_html = "<b>(*)</b> " | |
90 self.setHTML("%(wait)s%(name)s" % {'wait': _wait_html, | |
91 'name': html_tools.html_sanitize(self.name)}) | |
92 | |
93 def setMessageWaiting(self, waiting): | |
94 """Show a visual indicator if message are waiting | |
95 @param waiting: True if message are waiting""" | |
96 self.waiting = waiting | |
97 self._fill() | |
98 | |
99 def onClick(self, sender): | |
100 self.host.getOrCreateLiberviaWidget(panels.ChatPanel, self.jid) | |
101 | |
102 | |
103 class GroupList(VerticalPanel): | |
104 | |
105 def __init__(self, parent): | |
106 VerticalPanel.__init__(self) | |
107 self.setStyleName('groupList') | |
108 self._parent = parent | |
109 | |
110 def add(self, group): | |
111 _item = GroupLabel(self._parent.host, group) | |
112 _item.addMouseListener(self._parent) | |
113 DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer") | |
114 index = 0 | |
115 for group_ in [child.group for child in self.getChildren()]: | |
116 if group_ > group: | |
117 break | |
118 index += 1 | |
119 VerticalPanel.insert(self, _item, index) | |
120 | |
121 def remove(self, group): | |
122 for wid in self: | |
123 if isinstance(wid, GroupLabel) and wid.group == group: | |
124 VerticalPanel.remove(self, wid) | |
125 | |
126 | |
127 class GenericContactList(VerticalPanel): | |
128 """Class that can be used to represent a contact list, but not necessarily | |
129 the one that is displayed on the left side. Special features like popup menu | |
130 panel or changing the contact states must be done in a sub-class.""" | |
131 | |
132 def __init__(self, host, handleClick=False): | |
133 VerticalPanel.__init__(self) | |
134 self.host = host | |
135 self.contacts = [] | |
136 self.handleClick = handleClick | |
137 | |
138 def add(self, jid, name=None, item_cb=None): | |
139 if jid in self.contacts: | |
140 return | |
141 index = 0 | |
142 for contact_ in self.contacts: | |
143 if contact_ > jid: | |
144 break | |
145 index += 1 | |
146 self.contacts.insert(index, jid) | |
147 _item = ContactLabel(self.host, jid, name, handleClick=self.handleClick) | |
148 DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer") | |
149 VerticalPanel.insert(self, _item, index) | |
150 if item_cb is not None: | |
151 item_cb(_item) | |
152 | |
153 def remove(self, jid): | |
154 wid = self.getContactLabel(jid) | |
155 if not wid: | |
156 return | |
157 VerticalPanel.remove(self, wid) | |
158 self.contacts.remove(jid) | |
159 | |
160 def isContactPresent(self, contact_jid): | |
161 """Return True if a contact is present in the panel""" | |
162 return contact_jid in self.contacts | |
163 | |
164 def getContacts(self): | |
165 return self.contacts | |
166 | |
167 def getContactLabel(self, contact_jid): | |
168 """get contactList widget of a contact | |
169 @return: ContactLabel item if present, else None""" | |
170 for wid in self: | |
171 if isinstance(wid, ContactLabel) and wid.jid == contact_jid: | |
172 return wid | |
173 return None | |
174 | |
175 | |
176 class ContactList(GenericContactList): | |
177 """The contact list that is displayed on the left side.""" | |
178 | |
179 def __init__(self, host): | |
180 GenericContactList.__init__(self, host, handleClick=True) | |
181 self.menu_entries = {"blog": {"title": "Public blog..."}} | |
182 self.context_menu = base_panels.PopupMenuPanel(entries=self.menu_entries, | |
183 hide=self.contextMenuHide, | |
184 callback=self.contextMenuCallback, | |
185 vertical=False, style={"selected": "menu-selected"}) | |
186 | |
187 def contextMenuHide(self, sender, key): | |
188 """Return True if the item for that sender should be hidden.""" | |
189 # TODO: enable the blogs of users that are on another server | |
190 return JID(sender.jid).domain != self.host._defaultDomain | |
191 | |
192 def contextMenuCallback(self, sender, key): | |
193 if key == "blog": | |
194 # TODO: use the bare when all blogs can be retrieved | |
195 node = JID(sender.jid).node | |
196 web_panel = panels.WebPanel(self.host, "/blog/%s" % node) | |
197 self.host.addTab("%s's blog" % node, web_panel) | |
198 else: | |
199 sender.onClick(sender) | |
200 | |
201 def add(self, jid_s, name=None): | |
202 """Add a contact | |
203 | |
204 @param jid_s (str): JID as unicode | |
205 @param name (str): nickname | |
206 """ | |
207 def item_cb(item): | |
208 self.context_menu.registerRightClickSender(item) | |
209 GenericContactList.add(self, jid_s, name, item_cb) | |
210 | |
211 def setState(self, jid, type_, state): | |
212 """Change the appearance of the contact, according to the state | |
213 @param jid: jid which need to change state | |
214 @param type_: one of availability, messageWaiting | |
215 @param state: | |
216 - for messageWaiting type: | |
217 True if message are waiting | |
218 - for availability type: | |
219 'unavailable' if not connected, else presence like RFC6121 #4.7.2.1""" | |
220 _item = self.getContactLabel(jid) | |
221 if _item: | |
222 if type_ == 'availability': | |
223 setPresenceStyle(_item, state) | |
224 elif type_ == 'messageWaiting': | |
225 _item.setMessageWaiting(state) | |
226 | |
227 | |
228 class ContactTitleLabel(base_widget.DragLabel, Label, ClickHandler): | |
229 def __init__(self, host, text): | |
230 Label.__init__(self, text) # , Element=DOM.createElement('div') | |
231 self.host = host | |
232 self.setStyleName('contactTitle') | |
233 base_widget.DragLabel.__init__(self, text, "CONTACT_TITLE") | |
234 ClickHandler.__init__(self) | |
235 self.addClickListener(self) | |
236 | |
237 def onClick(self, sender): | |
238 self.host.getOrCreateLiberviaWidget(panels.MicroblogPanel, None) | |
239 | |
240 | |
241 class ContactPanel(SimplePanel): | |
242 """Manage the contacts and groups""" | |
243 | |
244 def __init__(self, host): | |
245 SimplePanel.__init__(self) | |
246 | |
247 self.scroll_panel = ScrollPanel() | |
248 | |
249 self.host = host | |
250 self.groups = {} | |
251 self.connected = {} # jid connected as key and their status | |
252 | |
253 self.vPanel = VerticalPanel() | |
254 _title = ContactTitleLabel(host, 'Contacts') | |
255 DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer") | |
256 | |
257 self._contact_list = ContactList(host) | |
258 self._contact_list.setStyleName('contactList') | |
259 self._groupList = GroupList(self) | |
260 self._groupList.setStyleName('groupList') | |
261 | |
262 self.vPanel.add(_title) | |
263 self.vPanel.add(self._groupList) | |
264 self.vPanel.add(self._contact_list) | |
265 self.scroll_panel.add(self.vPanel) | |
266 self.add(self.scroll_panel) | |
267 self.setStyleName('contactBox') | |
268 Window.addWindowResizeListener(self) | |
269 | |
270 def onWindowResized(self, width, height): | |
271 contact_panel_elt = self.getElement() | |
272 classname = 'widgetsPanel' if isinstance(self.getParent().getParent(), panels.UniBoxPanel) else'gwt-TabBar' | |
273 _elts = doc().getElementsByClassName(classname) | |
274 if not _elts.length: | |
275 log.error("no element of class %s found, it should exist !" % classname) | |
276 tab_bar_h = height | |
277 else: | |
278 tab_bar_h = DOM.getAbsoluteTop(_elts.item(0)) or height # getAbsoluteTop can be 0 if tabBar is hidden | |
279 | |
280 ideal_height = tab_bar_h - DOM.getAbsoluteTop(contact_panel_elt) - 5 | |
281 self.scroll_panel.setHeight("%s%s" % (ideal_height, "px")) | |
282 | |
283 def updateContact(self, jid_s, attributes, groups): | |
284 """Add a contact to the panel if it doesn't exist, update it else | |
285 @param jid_s: jid userhost as unicode | |
286 @param attributes: cf SàT Bridge API's newContact | |
287 @param groups: list of groups""" | |
288 _current_groups = self.getContactGroups(jid_s) | |
289 _new_groups = set(groups) | |
290 _key = "@%s: " | |
291 | |
292 for group in _current_groups.difference(_new_groups): | |
293 # We remove the contact from the groups where he isn't anymore | |
294 self.groups[group].remove(jid_s) | |
295 if not self.groups[group]: | |
296 # The group is now empty, we must remove it | |
297 del self.groups[group] | |
298 self._groupList.remove(group) | |
299 if self.host.uni_box: | |
300 self.host.uni_box.removeKey(_key % group) | |
301 | |
302 for group in _new_groups.difference(_current_groups): | |
303 # We add the contact to the groups he joined | |
304 if not group in self.groups.keys(): | |
305 self.groups[group] = set() | |
306 self._groupList.add(group) | |
307 if self.host.uni_box: | |
308 self.host.uni_box.addKey(_key % group) | |
309 self.groups[group].add(jid_s) | |
310 | |
311 # We add the contact to contact list, it will check if contact already exists | |
312 self._contact_list.add(jid_s) | |
313 | |
314 def removeContact(self, jid): | |
315 """Remove contacts from groups where he is and contact list""" | |
316 self.updateContact(jid, {}, []) # we remove contact from every group | |
317 self._contact_list.remove(jid) | |
318 | |
319 def setConnected(self, jid, resource, availability, priority, statuses): | |
320 """Set connection status | |
321 @param jid: JID userhost as unicode | |
322 """ | |
323 if availability == 'unavailable': | |
324 if jid in self.connected: | |
325 if resource in self.connected[jid]: | |
326 del self.connected[jid][resource] | |
327 if not self.connected[jid]: | |
328 del self.connected[jid] | |
329 else: | |
330 if not jid in self.connected: | |
331 self.connected[jid] = {} | |
332 self.connected[jid][resource] = (availability, priority, statuses) | |
333 | |
334 # check if the contact is connected with another resource, use the one with highest priority | |
335 if jid in self.connected: | |
336 max_resource = max_priority = None | |
337 for tmp_resource in self.connected[jid]: | |
338 if max_priority is None or self.connected[jid][tmp_resource][1] >= max_priority: | |
339 max_resource = tmp_resource | |
340 max_priority = self.connected[jid][tmp_resource][1] | |
341 if availability == "unavailable": # do not check the priority here, because 'unavailable' has a dummy one | |
342 priority = max_priority | |
343 availability = self.connected[jid][max_resource][0] | |
344 if jid not in self.connected or priority >= max_priority: | |
345 # case 1: jid not in self.connected means all resources are disconnected, update with 'unavailable' | |
346 # case 2: update (or confirm) with the values of the resource which takes precedence | |
347 self._contact_list.setState(jid, "availability", availability) | |
348 | |
349 # update the connected contacts chooser live | |
350 if hasattr(self.host, "room_contacts_chooser") and self.host.room_contacts_chooser is not None: | |
351 self.host.room_contacts_chooser.resetContacts() | |
352 | |
353 def setContactMessageWaiting(self, jid, waiting): | |
354 """Show an visual indicator that contact has send a message | |
355 @param jid: jid of the contact | |
356 @param waiting: True if message are waiting""" | |
357 self._contact_list.setState(jid, "messageWaiting", waiting) | |
358 | |
359 def getConnected(self, filter_muc=False): | |
360 """return a list of all jid (bare jid) connected | |
361 @param filter_muc: if True, remove the groups from the list | |
362 """ | |
363 contacts = self.connected.keys() | |
364 contacts.sort() | |
365 return contacts if not filter_muc else list(set(contacts).intersection(set(self.getContacts()))) | |
366 | |
367 def getContactGroups(self, contact_jid_s): | |
368 """Get groups where contact is | |
369 @param group: string of single group, or list of string | |
370 @param contact_jid_s: jid to test, as unicode | |
371 """ | |
372 result = set() | |
373 for group in self.groups: | |
374 if self.isContactInGroup(group, contact_jid_s): | |
375 result.add(group) | |
376 return result | |
377 | |
378 def isContactInGroup(self, group, contact_jid): | |
379 """Test if the contact_jid is in the group | |
380 @param group: string of single group, or list of string | |
381 @param contact_jid: jid to test | |
382 @return: True if contact_jid is in on of the groups""" | |
383 if group in self.groups and contact_jid in self.groups[group]: | |
384 return True | |
385 return False | |
386 | |
387 def isContactInRoster(self, contact_jid): | |
388 """Test if the contact is in our roster list""" | |
389 for _contact_label in self._contact_list: | |
390 if contact_jid == _contact_label.jid: | |
391 return True | |
392 return False | |
393 | |
394 def getContacts(self): | |
395 return self._contact_list.getContacts() | |
396 | |
397 def getGroups(self): | |
398 return self.groups.keys() | |
399 | |
400 def onMouseMove(self, sender, x, y): | |
401 pass | |
402 | |
403 def onMouseDown(self, sender, x, y): | |
404 pass | |
405 | |
406 def onMouseUp(self, sender, x, y): | |
407 pass | |
408 | |
409 def onMouseEnter(self, sender): | |
410 if isinstance(sender, GroupLabel): | |
411 for contact in self._contact_list: | |
412 if contact.jid in self.groups[sender.group]: | |
413 contact.addStyleName("selected") | |
414 | |
415 def onMouseLeave(self, sender): | |
416 if isinstance(sender, GroupLabel): | |
417 for contact in self._contact_list: | |
418 if contact.jid in self.groups[sender.group]: | |
419 contact.removeStyleName("selected") |