Mercurial > libervia-web
diff src/browser/contact.py @ 449:981ed669d3b3
/!\ reorganize all the file hierarchy, move the code and launching script to src:
- browser_side --> src/browser
- public --> src/browser_side/public
- libervia.py --> src/browser/libervia_main.py
- libervia_server --> src/server
- libervia_server/libervia.sh --> src/libervia.sh
- twisted --> src/twisted
- new module src/common
- split constants.py in 3 files:
- src/common/constants.py
- src/browser/constants.py
- src/server/constants.py
- output --> html (generated by pyjsbuild during the installation)
- new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css
- setup.py installs libervia to the following paths:
- src/common --> <LIB>/libervia/common
- src/server --> <LIB>/libervia/server
- src/twisted --> <LIB>/twisted
- html --> <SHARE>/libervia/html
- server_side --> <SHARE>libervia/server_side
- LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation:
- clean: remove previous installation directories
- purge: remove building and previous installation directories
You may need to update your sat.conf and/or launching script to update the following options/parameters:
- ssl_certificate
- data_dir
author | souliane <souliane@mailoo.org> |
---|---|
date | Tue, 20 May 2014 06:41:16 +0200 |
parents | browser_side/contact.py@d52f529a6d42 |
children | 1a0cec9b0f1e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/browser/contact.py Tue May 20 06:41:16 2014 +0200 @@ -0,0 +1,415 @@ +#!/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.ClickListener import ClickHandler +from pyjamas.ui.Label import Label +from pyjamas.ui.HTML import HTML +from pyjamas import Window +from pyjamas import DOM +from __pyjamas__ import doc + +from jid import JID + +from base_panels import PopupMenuPanel +from base_widget import DragLabel +from panels import ChatPanel, MicroblogPanel, WebPanel, UniBoxPanel +from html_tools import html_sanitize + + +def setPresenceStyle(element, presence, base_style="contact"): + """ + Set the CSS style of a contact's element according to its presence. + @param item: the UI element of the contact + @param presence: a value in ("", "chat", "away", "dnd", "xa"). + @param base_style: the base name of the style to apply + """ + if not hasattr(element, 'presence_style'): + element.presence_style = None + style = '%s-%s' % (base_style, presence or 'connected') + if style == element.presence_style: + return + if element.presence_style is not None: + element.removeStyleName(element.presence_style) + element.addStyleName(style) + element.presence_style = style + + +class GroupLabel(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') + DragLabel.__init__(self, group, "GROUP") + ClickHandler.__init__(self) + self.addClickListener(self) + + def onClick(self, sender): + self.host.getOrCreateLiberviaWidget(MicroblogPanel, self.group) + + +class ContactLabel(DragLabel, HTML, ClickHandler): + def __init__(self, host, jid, name=None, handleClick=True): + HTML.__init__(self) + self.host = host + self.name = name or jid + self.waiting = False + self.jid = jid + self._fill() + self.setStyleName('contact') + DragLabel.__init__(self, jid, "CONTACT") + if handleClick: + ClickHandler.__init__(self) + self.addClickListener(self) + + def _fill(self): + if self.waiting: + _wait_html = "<b>(*)</b> " + self.setHTML("%(wait)s%(name)s" % {'wait': _wait_html, + 'name': 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._fill() + + def onClick(self, sender): + self.host.getOrCreateLiberviaWidget(ChatPanel, 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 [group.group for group 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) + + +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, handleClick=False): + VerticalPanel.__init__(self) + self.host = host + self.contacts = [] + self.handleClick = handleClick + + def add(self, jid, name=None, item_cb=None): + if jid in self.contacts: + return + index = 0 + for contact_ in self.contacts: + if contact_ > jid: + break + index += 1 + self.contacts.insert(index, jid) + _item = ContactLabel(self.host, jid, name, handleClick=self.handleClick) + DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer") + VerticalPanel.insert(self, _item, index) + if item_cb is not None: + item_cb(_item) + + def remove(self, jid): + wid = self.getContactLabel(jid) + if not wid: + return + VerticalPanel.remove(self, wid) + self.contacts.remove(jid) + + 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 getContactLabel(self, contact_jid): + """get contactList widget of a contact + @return: ContactLabel item if present, else None""" + for wid in self: + if isinstance(wid, ContactLabel) and wid.jid == contact_jid: + return wid + return None + + +class ContactList(GenericContactList): + """The contact list that is displayed on the left side.""" + + def __init__(self, host): + GenericContactList.__init__(self, host, handleClick=True) + self.menu_entries = {"blog": {"title": "Public blog..."}} + self.context_menu = PopupMenuPanel(entries=self.menu_entries, + hide=self.contextMenuHide, + callback=self.contextMenuCallback, + vertical=False, style={"selected": "menu-selected"}) + + def contextMenuHide(self, sender, key): + """Return True if the item for that sender should be hidden.""" + # TODO: enable the blogs of users that are on another server + return JID(sender.jid).domain != self.host._defaultDomain + + def contextMenuCallback(self, sender, key): + if key == "blog": + # TODO: use the bare when all blogs can be retrieved + node = JID(sender.jid).node + web_panel = WebPanel(self.host, "/blog/%s" % node) + self.host.addTab("%s's blog" % node, web_panel) + else: + sender.onClick(sender) + + def add(self, jid, name=None): + def item_cb(item): + self.context_menu.registerRightClickSender(item) + GenericContactList.add(self, jid, name, item_cb) + + 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""" + _item = self.getContactLabel(jid) + if _item: + if type_ == 'availability': + setPresenceStyle(_item, state) + elif type_ == 'messageWaiting': + _item.setMessageWaiting(state) + + +class ContactTitleLabel(DragLabel, Label, ClickHandler): + def __init__(self, host, text): + Label.__init__(self, text) #, Element=DOM.createElement('div') + self.host = host + self.setStyleName('contactTitle') + DragLabel.__init__(self, text, "CONTACT_TITLE") + ClickHandler.__init__(self) + self.addClickListener(self) + + def onClick(self, sender): + self.host.getOrCreateLiberviaWidget(MicroblogPanel, 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('contactBox') + Window.addWindowResizeListener(self) + + def onWindowResized(self, width, height): + contact_panel_elt = self.getElement() + classname = 'widgetsPanel' if isinstance(self.getParent().getParent(), 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, attributes, groups): + """Add a contact to the panel if it doesn't exist, update it else + @param jid: jid userhost as unicode + @attributes: cf SàT Bridge API's newContact + @param groups: list of groups""" + _current_groups = self.getContactGroups(jid) + _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) + 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 not group 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) + + # We add the contact to contact list, it will check if contact already exists + self._contact_list.add(jid) + + 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, resource, availability, priority, statuses): + """Set connection status + @param jid: JID userhost as unicode + """ + if availability == 'unavailable': + if jid in self.connected: + if resource in self.connected[jid]: + del self.connected[jid][resource] + if not self.connected[jid]: + del self.connected[jid] + else: + if not jid in self.connected: + self.connected[jid] = {} + self.connected[jid][resource] = (availability, priority, statuses) + + # check if the contact is connected with another resource, use the one with highest priority + if jid in self.connected: + max_resource = max_priority = None + for tmp_resource in self.connected[jid]: + if max_priority is None or self.connected[jid][tmp_resource][1] >= max_priority: + max_resource = tmp_resource + max_priority = self.connected[jid][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][max_resource][0] + if jid 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, "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() + + 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): + """Get groups where contact is + @param group: string of single group, or list of string + @param contact_jid: jid to test + """ + result = set() + for group in self.groups: + if self.isContactInGroup(group, contact_jid): + 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_label in self._contact_list: + if contact_jid == _contact_label.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.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.removeStyleName("selected") +