Mercurial > libervia-web
view src/browser/sat_browser/contact.py @ 583:4c6c61696ba0
plugin sec_otr: improvement when sending a message to a MUC
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 22 Oct 2014 14:19:25 +0200 |
parents | 668bb04e9708 |
children |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # Libervia: a Salut à Toi frontend # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import pyjd # this is dummy in pyjs from sat.core.log import getLogger log = getLogger(__name__) from pyjamas.ui.SimplePanel import SimplePanel from pyjamas.ui.ScrollPanel import ScrollPanel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.ClickListener import ClickHandler from pyjamas.ui.Label import Label from pyjamas.ui.HTML import HTML from pyjamas.ui.Image import Image from pyjamas import Window from pyjamas import DOM from __pyjamas__ import doc from constants import Const as C from jid import JID import base_widget import panels import html_tools def buildPresenceStyle(presence, base_style=None): """Return the CSS classname to be used for displaying the given presence information. @param presence (str): presence is a value in ('', 'chat', 'away', 'dnd', 'xa') @param base_style (str): base classname @return: str """ if not base_style: base_style = "contactLabel" return '%s-%s' % (base_style, presence or 'connected') def setPresenceStyle(widget, presence, base_style=None): """ Set the CSS style of a contact's element according to its presence. @param widget (Widget): the UI element of the contact @param presence (str): a value in ("", "chat", "away", "dnd", "xa"). @param base_style (str): the base name of the style to apply """ if not hasattr(widget, 'presence_style'): widget.presence_style = None style = buildPresenceStyle(presence, base_style) if style == widget.presence_style: return if widget.presence_style is not None: widget.removeStyleName(widget.presence_style) widget.addStyleName(style) widget.presence_style = style class GroupLabel(base_widget.DragLabel, Label, ClickHandler): def __init__(self, host, group): self.group = group self.host = host Label.__init__(self, group) # , Element=DOM.createElement('div') self.setStyleName('group') base_widget.DragLabel.__init__(self, group, "GROUP") ClickHandler.__init__(self) self.addClickListener(self) def onClick(self, sender): self.host.getOrCreateLiberviaWidget(panels.MicroblogPanel, {'item': self.group}) class ContactLabel(HTML): def __init__(self, jid, name=None): HTML.__init__(self) self.name = name or jid self.waiting = False self.refresh() self.setStyleName('contactLabel') def refresh(self): if self.waiting: wait_html = "<b>(*)</b> " self.setHTML("%(wait)s%(name)s" % {'wait': wait_html, 'name': html_tools.html_sanitize(self.name)}) def setMessageWaiting(self, waiting): """Show a visual indicator if message are waiting @param waiting: True if message are waiting""" self.waiting = waiting self.refresh() class ContactMenuBar(base_widget.WidgetMenuBar): def onBrowserEvent(self, event): base_widget.WidgetMenuBar.onBrowserEvent(self, event) event.stopPropagation() # prevent opening the chat dialog @classmethod def getCategoryHTML(cls, menu_name_i18n, type_): return '<img src="%s"/>' % C.DEFAULT_AVATAR def setUrl(self, url): """Set the URL of the contact avatar.""" self.items[0].setHTML('<img src="%s" />' % url) class ContactBox(VerticalPanel, ClickHandler, base_widget.DragLabel): def __init__(self, host, jid, name=None, click_listener=None, handle_menu=None): VerticalPanel.__init__(self, StyleName='contactBox', VerticalAlignment='middle') base_widget.DragLabel.__init__(self, jid, "CONTACT") self.host = host self.jid = jid self.label = ContactLabel(jid, name) self.avatar = ContactMenuBar(self, host) if handle_menu else Image() self.updateAvatar(host.getAvatar(jid)) self.add(self.avatar) self.add(self.label) if click_listener: ClickHandler.__init__(self) self.addClickListener(self) self.click_listener = click_listener def addMenus(self, menu_bar): menu_bar.addCachedMenus(C.MENU_ROSTER_JID_CONTEXT, {'jid': self.jid}) menu_bar.addCachedMenus(C.MENU_JID_CONTEXT, {'jid': self.jid}) def setMessageWaiting(self, waiting): """Show a visual indicator if message are waiting @param waiting: True if message are waiting""" self.label.setMessageWaiting(waiting) def updateAvatar(self, url): """Update the avatar. @param url (str): image url """ self.avatar.setUrl(url) def onClick(self, sender): self.click_listener(self.jid) class GroupList(VerticalPanel): def __init__(self, parent): VerticalPanel.__init__(self) self.setStyleName('groupList') self._parent = parent def add(self, group): _item = GroupLabel(self._parent.host, group) _item.addMouseListener(self._parent) DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer") index = 0 for group_ in [child.group for child in self.getChildren()]: if group_ > group: break index += 1 VerticalPanel.insert(self, _item, index) def remove(self, group): for wid in self: if isinstance(wid, GroupLabel) and wid.group == group: VerticalPanel.remove(self, wid) return def getGroupBox(self, group): """get the widget of a group @param group (str): the group @return: GroupLabel instance if present, else None""" for wid in self: if isinstance(wid, GroupLabel) and wid.group == group: return wid return None class GenericContactList(VerticalPanel): """Class that can be used to represent a contact list, but not necessarily the one that is displayed on the left side. Special features like popup menu panel or changing the contact states must be done in a sub-class.""" def __init__(self, host, handle_click=False, handle_menu=False): VerticalPanel.__init__(self) self.host = host self.contacts = [] self.click_listener = None self.handle_menu = handle_menu if handle_click: def cb(contact_jid): self.host.getOrCreateLiberviaWidget(panels.ChatPanel, {'item': contact_jid}) self.click_listener = cb def add(self, jid_s, name=None): """Add a contact to the list. @param jid (str): JID of the contact @param name (str): optional name of the contact """ assert(isinstance(jid_s, str)) if jid_s in self.contacts: return index = 0 for contact_ in self.contacts: if contact_ > jid_s: break index += 1 self.contacts.insert(index, jid_s) box = ContactBox(self.host, jid_s, name, self.click_listener, self.handle_menu) VerticalPanel.insert(self, box, index) def remove(self, jid_s): box = self.getContactBox(jid_s) if not box: return VerticalPanel.remove(self, box) self.contacts.remove(jid_s) def isContactPresent(self, contact_jid): """Return True if a contact is present in the panel""" return contact_jid in self.contacts def getContacts(self): return self.contacts def getContactBox(self, contact_jid_s): """get the widget of a contact @param contact_jid_s (str): the contact @return: ContactBox instance if present, else None""" for wid in self: if isinstance(wid, ContactBox) and wid.jid == contact_jid_s: return wid return None def updateAvatar(self, jid_s, url): """Update the avatar of the given contact @param jid_s (str): contact jid @param url (str): image url """ try: self.getContactBox(jid_s).updateAvatar(url) except TypeError: pass class ContactList(GenericContactList): """The contact list that is displayed on the left side.""" def __init__(self, host): GenericContactList.__init__(self, host, handle_click=True, handle_menu=True) def setState(self, jid, type_, state): """Change the appearance of the contact, according to the state @param jid: jid which need to change state @param type_: one of availability, messageWaiting @param state: - for messageWaiting type: True if message are waiting - for availability type: 'unavailable' if not connected, else presence like RFC6121 #4.7.2.1""" contact_box = self.getContactBox(jid) if contact_box: if type_ == 'availability': setPresenceStyle(contact_box.label, state) elif type_ == 'messageWaiting': contact_box.setMessageWaiting(state) class ContactTitleLabel(base_widget.DragLabel, Label, ClickHandler): def __init__(self, host, text): Label.__init__(self, text) # , Element=DOM.createElement('div') self.host = host self.setStyleName('contactTitle') base_widget.DragLabel.__init__(self, text, "CONTACT_TITLE") ClickHandler.__init__(self) self.addClickListener(self) def onClick(self, sender): self.host.getOrCreateLiberviaWidget(panels.MicroblogPanel, {'item': None}) class ContactPanel(SimplePanel): """Manage the contacts and groups""" def __init__(self, host): SimplePanel.__init__(self) self.scroll_panel = ScrollPanel() self.host = host self.groups = {} self.connected = {} # jid connected as key and their status self.vPanel = VerticalPanel() _title = ContactTitleLabel(host, 'Contacts') DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer") self._contact_list = ContactList(host) self._contact_list.setStyleName('contactList') self._groupList = GroupList(self) self._groupList.setStyleName('groupList') self.vPanel.add(_title) self.vPanel.add(self._groupList) self.vPanel.add(self._contact_list) self.scroll_panel.add(self.vPanel) self.add(self.scroll_panel) self.setStyleName('contactPanel') Window.addWindowResizeListener(self) def onWindowResized(self, width, height): contact_panel_elt = self.getElement() classname = 'widgetsPanel' if isinstance(self.getParent().getParent(), panels.UniBoxPanel) else'gwt-TabBar' _elts = doc().getElementsByClassName(classname) if not _elts.length: log.error("no element of class %s found, it should exist !" % classname) tab_bar_h = height else: tab_bar_h = DOM.getAbsoluteTop(_elts.item(0)) or height # getAbsoluteTop can be 0 if tabBar is hidden ideal_height = tab_bar_h - DOM.getAbsoluteTop(contact_panel_elt) - 5 self.scroll_panel.setHeight("%s%s" % (ideal_height, "px")) def updateContact(self, jid_s, attributes, groups): """Add a contact to the panel if it doesn't exist, update it else @param jid_s: jid userhost as unicode @param attributes: cf SàT Bridge API's newContact @param groups: list of groups""" _current_groups = self.getContactGroups(jid_s) _new_groups = set(groups) _key = "@%s: " for group in _current_groups.difference(_new_groups): # We remove the contact from the groups where he isn't anymore self.groups[group].remove(jid_s) if not self.groups[group]: # The group is now empty, we must remove it del self.groups[group] self._groupList.remove(group) if self.host.uni_box: self.host.uni_box.removeKey(_key % group) for group in _new_groups.difference(_current_groups): # We add the contact to the groups he joined if group not in self.groups.keys(): self.groups[group] = set() self._groupList.add(group) if self.host.uni_box: self.host.uni_box.addKey(_key % group) self.groups[group].add(jid_s) # We add the contact to contact list, it will check if contact already exists self._contact_list.add(jid_s) self.updateVisibility([jid_s], self.getContactGroups(jid_s)) def removeContact(self, jid): """Remove contacts from groups where he is and contact list""" self.updateContact(jid, {}, []) # we remove contact from every group self._contact_list.remove(jid) def setConnected(self, jid_s, resource, availability, priority, statuses): """Set connection status @param jid_s (str): JID userhost as unicode """ if availability == 'unavailable': if jid_s in self.connected: if resource in self.connected[jid_s]: del self.connected[jid_s][resource] if not self.connected[jid_s]: del self.connected[jid_s] else: if jid_s not in self.connected: self.connected[jid_s] = {} self.connected[jid_s][resource] = (availability, priority, statuses) # check if the contact is connected with another resource, use the one with highest priority if jid_s in self.connected: max_resource = max_priority = None for tmp_resource in self.connected[jid_s]: if max_priority is None or self.connected[jid_s][tmp_resource][1] >= max_priority: max_resource = tmp_resource max_priority = self.connected[jid_s][tmp_resource][1] if availability == "unavailable": # do not check the priority here, because 'unavailable' has a dummy one priority = max_priority availability = self.connected[jid_s][max_resource][0] if jid_s not in self.connected or priority >= max_priority: # case 1: jid not in self.connected means all resources are disconnected, update with 'unavailable' # case 2: update (or confirm) with the values of the resource which takes precedence self._contact_list.setState(jid_s, "availability", availability) # update the connected contacts chooser live if hasattr(self.host, "room_contacts_chooser") and self.host.room_contacts_chooser is not None: self.host.room_contacts_chooser.resetContacts() self.updateVisibility([jid_s], self.getContactGroups(jid_s)) def setContactMessageWaiting(self, jid, waiting): """Show an visual indicator that contact has send a message @param jid: jid of the contact @param waiting: True if message are waiting""" self._contact_list.setState(jid, "messageWaiting", waiting) def getConnected(self, filter_muc=False): """return a list of all jid (bare jid) connected @param filter_muc: if True, remove the groups from the list """ contacts = self.connected.keys() contacts.sort() return contacts if not filter_muc else list(set(contacts).intersection(set(self.getContacts()))) def getContactGroups(self, contact_jid_s): """Get groups where contact is @param group: string of single group, or list of string @param contact_jid_s: jid to test, as unicode """ result = set() for group in self.groups: if self.isContactInGroup(group, contact_jid_s): result.add(group) return result def isContactInGroup(self, group, contact_jid): """Test if the contact_jid is in the group @param group: string of single group, or list of string @param contact_jid: jid to test @return: True if contact_jid is in on of the groups""" if group in self.groups and contact_jid in self.groups[group]: return True return False def isContactInRoster(self, contact_jid): """Test if the contact is in our roster list""" for contact_box in self._contact_list: if contact_jid == contact_box.jid: return True return False def getContacts(self): return self._contact_list.getContacts() def getGroups(self): return self.groups.keys() def onMouseMove(self, sender, x, y): pass def onMouseDown(self, sender, x, y): pass def onMouseUp(self, sender, x, y): pass def onMouseEnter(self, sender): if isinstance(sender, GroupLabel): for contact in self._contact_list: if contact.jid in self.groups[sender.group]: contact.label.addStyleName("selected") def onMouseLeave(self, sender): if isinstance(sender, GroupLabel): for contact in self._contact_list: if contact.jid in self.groups[sender.group]: contact.label.removeStyleName("selected") def updateAvatar(self, jid_s, url): """Update the avatar of the given contact @param jid_s (str): contact jid @param url (str): image url """ self._contact_list.updateAvatar(jid_s, url) def hasVisibleMembers(self, group): """Tell if the given group actually has visible members @param group (str): the group to check @return: boolean """ for jid in self.groups[group]: if self._contact_list.getContactBox(jid).isVisible(): return True return False def offlineContactsToShow(self): """Tell if offline contacts should be visible according to the user settings @return: boolean """ return self.host.getCachedParam('General', C.SHOW_OFFLINE_CONTACTS) == 'true' def emtyGroupsToShow(self): """Tell if empty groups should be visible according to the user settings @return: boolean """ return self.host.getCachedParam('General', C.SHOW_EMPTY_GROUPS) == 'true' def updateVisibility(self, jids, groups): """Set the widgets visibility for the given contacts and groups @param jids (list[str]): list of JID @param groups (list[str]): list of groups """ for jid_s in jids: try: self._contact_list.getContactBox(jid_s).setVisible(jid_s in self.connected or self.offlineContactsToShow()) except TypeError: log.warning('No box for contact %s: this code line should not be reached' % jid_s) for group in groups: try: self._groupList.getGroupBox(group).setVisible(self.hasVisibleMembers(group) or self.emtyGroupsToShow()) except TypeError: log.warning('No box for group %s: this code line should not be reached' % group) def refresh(self): """Show or hide disconnected contacts and empty groups""" self.updateVisibility(self._contact_list.contacts, self.groups.keys())