changeset 684:e876f493dccc

browser_side: follow changes made on quick_frontend for chat states and MUC symbols + minor fixes following the refactorisation: - some MUC handlers are no more needed, the presence handler is enough - move the chat states logic to quick_frontend - display MUC games symbols - remove classes contact_list.ContactsPanel, contact_panel.Occupant and contact_panel.OccupantsList - move buildPresenceStyle and setPresenceStyle to html_tools - fixes games menu callback
author souliane <souliane@mailoo.org>
date Wed, 18 Mar 2015 10:17:04 +0100
parents 801eb94aa869
children 9877607c719a
files src/browser/public/libervia.css src/browser/sat_browser/chat.py src/browser/sat_browser/contact_list.py src/browser/sat_browser/contact_panel.py src/browser/sat_browser/contact_widget.py src/browser/sat_browser/editor_widget.py src/browser/sat_browser/game_radiocol.py src/browser/sat_browser/game_tarot.py src/browser/sat_browser/html_tools.py src/browser/sat_browser/main_panel.py
diffstat 10 files changed, 138 insertions(+), 243 deletions(-) [+]
line wrap: on
line diff
--- a/src/browser/public/libervia.css	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/public/libervia.css	Wed Mar 18 10:17:04 2015 +0100
@@ -1054,17 +1054,6 @@
     color: #006600;
 }
 
-.occupant {
-    margin-top: 10px;
-    margin-right: 4px;
-    min-width: 120px;
-    padding: 5px 15px 5px 15px;
-    font-weight: bold;
-    background-color: #eee;
-    border: 1px solid #ddd;
-    white-space: nowrap;
-}
-
 .occupantsPanelCell {
     border-right: 2px dotted #ddd;
     padding-left: 5px;
--- a/src/browser/sat_browser/chat.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/chat.py	Wed Mar 18 10:17:04 2015 +0100
@@ -44,7 +44,6 @@
 import base_panel
 import contact_panel
 import editor_widget
-import contact_list
 from constants import Const as C
 import plugin_xep_0085
 import game_tarot
@@ -103,6 +102,9 @@
                                                                contacts_display=('resource',))
             chat_area.add(self.occupants_panel)
             DOM.setAttribute(chat_area.getWidgetTd(self.occupants_panel), "className", "occupantsPanelCell")
+            # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
+            self.presenceListener = self.onPresenceUpdate
+            self.host.addListener('presence', self.presenceListener, [C.PROF_KEY_NONE])
         self._body.add(chat_area)
         self.content = AbsolutePanel()
         self.content.setStyleName('chatContent')
@@ -113,8 +115,7 @@
         self.vpanel.setCellHeight(self._body, '100%')
         self.addStyleName('chatPanel')
         self.setWidget(self.vpanel)
-        self.state_machine = plugin_xep_0085.ChatStateMachine(self.host, unicode(self.target))
-        self._state = None
+        self.chat_state_machine = plugin_xep_0085.ChatStateMachine(self.host, unicode(self.target))
         self.refresh()
         if type_ == C.CHAT_ONE2ONE:
             self.historyPrint(profile=self.profile)
@@ -187,40 +188,45 @@
                               errback=self.host.sendError,
                               profile_key=C.PROF_KEY_NONE
                               )
-        self.state_machine._onEvent("active")
+        self.chat_state_machine._onEvent("active")
+
+    def onPresenceUpdate(self, entity, show, priority, statuses, profile):
+        """Update entity's presence status
+
+        @param entity(jid.JID): entity updated
+        @param show: availability
+        @parap priority: resource's priority
+        @param statuses: dict of statuses
+        @param profile: %(doc_profile)s
+        """
+        assert self.type == C.CHAT_GROUP
+        if entity.bare == self.target:
+            self.occupants_panel.setPresence(entity, show)
 
     def onQuit(self):
         libervia_widget.LiberviaWidget.onQuit(self)
         if self.type == C.CHAT_GROUP:
+            self.host.removeListener('presence', self.presenceListener)
             self.host.bridge.call('mucLeave', None, unicode(self.target.bare))
 
     def setUserNick(self, nick):
         """Set the nick of the user, usefull for e.g. change the color of the user"""
         self.nick = nick
 
-    def setPresents(self, nicks):
-        """Set the occupants of a group chat.
-
-        @param nicks (list[unicode]): sorted list of nicknames
-        """
-        QuickChat.setPresents(self, nicks)
-        self.occupants_panel.setList([jid.JID(u"%s/%s" % (self.target, nick)) for nick in nicks])
-
-    def replaceUser(self, nick, show_info=True):
+    def addUser(self, nick):
         """Add user if it is not in the group list"""
-        QuickChat.replaceUser(self, nick, show_info)
+        QuickChat.addUser(self, nick)
         occupant_jid = jid.JID("%s/%s" % (unicode(self.target), nick))
         self.occupants_panel.addContact(occupant_jid)
 
-    def removeUser(self, nick, show_info=True):
+    def removeUser(self, nick):
         """Remove a user from the group list"""
-        QuickChat.removeUser(self, nick, show_info)
+        QuickChat.removeUser(self, nick)
         occupant_jid = jid.JID("%s/%s" % (unicode(self.target), nick))
         self.occupants_panel.removeContact(occupant_jid)
 
     def changeUserNick(self, old_nick, new_nick):
         assert self.type == C.CHAT_GROUP
-        # FIXME
         # self.occupants_panel.removeOccupant(old_nick)
         # self.occupants_panel.addOccupant(new_nick)
         self.printInfo(_("%(old_nick)s is now known as %(new_nick)s") % {'old_nick': old_nick, 'new_nick': new_nick})
@@ -279,33 +285,18 @@
         self.content.add(ChatText(nick, mymess, msg, extra))
         self.content_scroll.scrollToBottom()
 
-    def setState(self, state, nick=None):
-        """Set the chat state (XEP-0085) of the contact. Leave nick to None
-        to set the state for a one2one conversation, or give a nickname or
-        C.ALL_OCCUPANTS to set the state of a participant within a MUC.
-        @param state: the new chat state
-        @param nick: ignored for one2one, otherwise the MUC user nick or C.ALL_OCCUPANTS
+    def setTitle(self, title=None, extra=None):
+        """Refresh the title of this Chat dialog
+
+        @param title (unicode): main title or None to use default
+        @param extra (dict{unicode: unicode}): extra info
         """
-        return # FIXME
-        if self.type == C.CHAT_GROUP:
-            assert(nick)
-            if nick == C.ALL_OCCUPANTS:
-                occupants = self.occupants_panel.occupants_panel.keys()
-            else:
-                occupants = [nick] if nick in self.occupants_panel.occupants_panel else []
-            for occupant in occupants:
-                self.occupants_panel.occupants_panel[occupant].setState(state)
-        else:
-            self._state = state
-            self.refreshTitle()
-        self.state_machine.started = not not state  # start to send "composing" state from now
-
-    def refreshTitle(self):
-        """Refresh the title of this Chat dialog"""
-        title = unicode(self.target.bare)
-        if self._state:
-            title += " (%s)".format(self._state)
-        self.setTitle(title)
+        if title is None:
+            title = unicode(self.target.bare)
+        if extra:
+            extra_title = ' '.join([u'({})'.format(value) for value in extra.values()])
+            title = '%s %s' % (title, extra_title)
+        libervia_widget.LiberviaWidget.setTitle(self, title)
 
     def setConnected(self, jid_s, resource, availability, priority, statuses):
         """Set connection status
@@ -317,11 +308,27 @@
             return
         box = self.occupants_panel.getOccupantBox(resource)
         if box:
-            contact_list.setPresenceStyle(box, availability)
+            html_tools.setPresenceStyle(box, availability)
+
+    def setOccupantStates(self, occupant_jid, states):
+        """Set a MUC occupant's states.
 
-    def updateChatState(self, from_jid, state):
-        #TODO
-        pass
+        @param occupant_jid (jid.JID): occupant to update
+        @param states (dict{unicode: unicode}): new states
+        """
+        self.occupants_panel.getContactBox(occupant_jid).updateStates(states)
+        if 'chat_state' in states.keys():  # start/stop sending "composing" state from now
+            self.chat_state_machine.started = not not states['chat_state']
+
+    def setContactStates(self, contact_jid, states):
+        """Set a one2one contact's states.
+
+        @param contact_jid (jid.JID): contact
+        @param states (dict{unicode: unicode}): new states
+        """
+        self.setTitle(extra=states)
+        if 'chat_state' in states.keys():  # start/stop sending "composing" state from now
+            self.chat_state_machine.started = not not states['chat_state']
 
     def addGamePanel(self, widget):
         """Insert a game panel to this Chat dialog.
--- a/src/browser/sat_browser/contact_list.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/contact_list.py	Wed Mar 18 10:17:04 2015 +0100
@@ -38,37 +38,6 @@
 unicode = str # XXX: pyjama doesn't manage unicode
 
 
-def buildPresenceStyle(presence, base_style=None):
-    """Return the CSS classname to be used for displaying the given presence information.
-
-    @param presence (unicode): presence is a value in ('', 'chat', 'away', 'dnd', 'xa')
-    @param base_style (unicode): base classname
-    @return: unicode
-    """
-    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 (unicode): a value in ("", "chat", "away", "dnd", "xa").
-    @param base_style (unicode): 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(libervia_widget.DragLabel, Label, ClickHandler):
     def __init__(self, host, group):
         """
@@ -132,37 +101,6 @@
         return self._groups
 
 
-class ContactsPanel(contact_panel.ContactsPanel):
-    """The contact list that is displayed on the left side."""
-
-    def __init__(self, host):
-
-        def on_click(contact_jid):
-            self.host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
-
-        contact_panel.ContactsPanel.__init__(self, host, contacts_click=on_click,
-                                             contacts_menus=(C.MENU_JID_CONTEXT, C.MENU_ROSTER_JID_CONTEXT))
-
-    def setState(self, jid_, type_, state):
-        """Change the appearance of the contact, according to the state
-
-        @param jid_ (jid.JID): jid.JID which need to change state
-        @param type_ (unicode): one of "availability", "messageWaiting"
-        @param state:
-            - for messageWaiting type:
-                True if message are waiting
-            - for availability type:
-                C.PRESENCE_UNAVAILABLE or None if not connected, else presence like RFC6121 #4.7.2.1"""
-        assert type_ in ('availability', 'messageWaiting')
-        contact_box = self.getContactBox(jid_)
-        if type_ == 'availability':
-            if state is None:
-                state = C.PRESENCE_UNAVAILABLE
-            setPresenceStyle(contact_box.label, state)
-        elif type_ == 'messageWaiting':
-            contact_box.setAlert(state)
-
-
 class ContactTitleLabel(libervia_widget.DragLabel, Label, ClickHandler):
 
     def __init__(self, host, text):
@@ -187,7 +125,11 @@
         self.vPanel = VerticalPanel()
         _title = ContactTitleLabel(host, 'Contacts')
         DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer")
-        self._contacts_panel = ContactsPanel(host)
+
+        def on_click(contact_jid):
+            self.host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
+
+        self._contacts_panel = contact_panel.ContactsPanel(host, contacts_click=on_click, contacts_menus=(C.MENU_JID_CONTEXT, C.MENU_ROSTER_JID_CONTEXT))
         self._contacts_panel.setStyleName('contactPanel') # FIXME: style doesn't exists !
         self._group_panel = GroupPanel(self)
 
@@ -235,7 +177,7 @@
         self._contacts_panel.setList(to_show)
 
         for jid_ in self._alerts:
-            self._contacts_panel.setState(jid_, "messageWaiting", True)
+            self._contacts_panel.getContactBox(jid_).setAlert(True)
 
     def remove(self, entity):
         # FIXME: SimplePanel and QuickContactList both have a 'remove' method
@@ -430,10 +372,7 @@
 
     def onPresenceUpdate(self, entity, show, priority, statuses, profile):
         QuickContactList.onPresenceUpdate(self, entity, show, priority, statuses, profile)
-        entity_bare = entity.bare
-        show = self.getCache(entity_bare, C.PRESENCE_SHOW) # we use cache to have the show nformation of main resource only
-        self._contacts_panel.setState(entity_bare, "availability", show)
-        self.update()  # FIXME: should update the list without rebuilding it all
+        self._contacts_panel.setPresence(entity, show)
 
     # def updateVisibility(self, jids, groups):
     #     """Set the widgets visibility for the given contacts and groups
--- a/src/browser/sat_browser/contact_panel.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/contact_panel.py	Wed Mar 18 10:17:04 2015 +0100
@@ -24,58 +24,13 @@
 log = getLogger(__name__)
 from sat_frontends.tools import jid
 
-from pyjamas.ui.AbsolutePanel import AbsolutePanel
 from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HTML import HTML
 
 import html_tools
 import contact_widget
 from constants import Const as C
 
 
-# FIXME: must be removed
-class Occupant(HTML):
-    """Occupant of a MUC room"""
-
-    def __init__(self, nick, state=None, special=""):
-        """
-        @param nick: the user nickname
-        @param state: the user chate state (XEP-0085)
-        @param special: a string of symbols (e.g: for activities)
-        """
-        HTML.__init__(self, StyleName="occupant")
-        self.nick = nick
-        self._state = state
-        self.special = special
-        self._refresh()
-
-    def __str__(self):
-        return self.nick
-
-    def setState(self, state):
-        self._state = state
-        self._refresh()
-
-    def addSpecial(self, special):
-        """@param special: unicode"""
-        if special not in self.special:
-            self.special += special
-            self._refresh()
-
-    def removeSpecials(self, special):
-        """@param special: unicode or list"""
-        if not isinstance(special, list):
-            special = [special]
-        for symbol in special:
-            self.special = self.special.replace(symbol, "")
-            self._refresh()
-
-    def _refresh(self):
-        state = (' %s' % C.MUC_USER_STATES[self._state]) if self._state else ''
-        special = "" if len(self.special) == 0 else " %s" % self.special
-        self.setHTML("%s%s%s" % (html_tools.html_sanitize(self.nick), special, state))
-
-
 class ContactsPanel(VerticalPanel):
     """ContactList graphic representation
 
@@ -118,6 +73,11 @@
         """
         return contact_jid.bare if self.merge_resources else contact_jid
 
+    def clear(self):
+        """Clear all contacts."""
+        self._contacts.clear()
+        VerticalPanel.clear(self)
+
     def setList(self, jids):
         """set all contacts in the list in one shot.
 
@@ -173,10 +133,7 @@
         @param contact_jid (jid.JID): contact JID
         @param url (unicode): image url
         """
-        try:
-            self.getContactBox(contact_jid).updateAvatar(url)
-        except TypeError:
-            pass
+        self.getContactBox(contact_jid).updateAvatar(url)
 
     def updateNick(self, contact_jid, new_nick):
         """Update the avatar of the given contact.
@@ -184,64 +141,17 @@
         @param contact_jid (jid.JID): contact JID
         @param new_nick (unicode): new nick of the contact
         """
-        try:
-            self.getContactBox(contact_jid).updateNick(new_nick)
-        except TypeError:
-            pass
-
-
-
-# FIXME: must be removed and ContactsPanel must be used instead
-class OccupantsList(AbsolutePanel):
-    """Panel user to show occupants of a room"""
+        self.getContactBox(contact_jid).updateNick(new_nick)
 
-    def __init__(self):
-        AbsolutePanel.__init__(self)
-        self.occupants_list = {}
-        self.setStyleName('occupantsList')
-
-    def addOccupant(self, nick):
-        if nick in self.occupants_list:
-            return
-        _occupant = Occupant(nick)
-        self.occupants_list[nick] = _occupant
-        self.add(_occupant)
-
-    def removeOccupant(self, nick):
-        try:
-            self.remove(self.occupants_list[nick])
-        except KeyError:
-            log.error("trying to remove an unexisting nick")
+    def setPresence(self, entity, show):
+        """Update entity's presence.
 
-    def getOccupantBox(self, nick):
-        """Get the widget element of the given nick.
-
-        @return: Occupant
+        @param entity(jid.JID): entity updated
+        @param show: availability
         """
-        try:
-            return self.occupants_list[nick]
-        except KeyError:
-            return None
-
-    def clear(self):
-        self.occupants_list.clear()
-        AbsolutePanel.clear(self)
-
-    def updateSpecials(self, occupants=[], html=""):
-        """Set the specified html "symbol" to the listed occupants,
-        and eventually remove it from the others (if they got it).
-        This is used for example to visualize who is playing a game.
-        @param occupants: list of the occupants that need the symbol
-        @param html: unicode symbol (actually one character or more)
-        or a list to assign different symbols of the same family.
-        """
-        index = 0
-        special = html
-        for occupant in self.occupants_list.keys():
-            if occupant in occupants:
-                if isinstance(html, list):
-                    special = html[index]
-                    index = (index + 1) % len(html)
-                self.occupants_list[occupant].addSpecial(special)
-            else:
-                self.occupants_list[occupant].removeSpecials(html)
+        if self.merge_resources:  # we use cache to have the show information of main resource only
+            clist = self.host.contact_list
+            show = clist.getCache(entity.bare, C.PRESENCE_SHOW)
+            if show is None:
+                show = C.PRESENCE_UNAVAILABLE
+        html_tools.setPresenceStyle(self.getContactBox(entity).label, show)
--- a/src/browser/sat_browser/contact_widget.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/contact_widget.py	Wed Mar 18 10:17:04 2015 +0100
@@ -74,7 +74,7 @@
             if contact_raw:
                 break
         if not contact_raw:
-            log.error(u"Counld not find a contact display for jid {jid} (display: {display})".format(jid=self.jid, display=self.display))
+            log.error(u"Could not find a contact display for jid {jid} (display: {display})".format(jid=self.jid, display=self.display))
             contact_raw = "UNNAMED"
         contact_html = html_tools.html_sanitize(contact_raw)
         html = "%(alert)s%(contact)s" % {'alert': alert_html,
@@ -132,6 +132,7 @@
         self.jid = jid_
         self.label = ContactLabel(host, self.jid, display=display)
         self.avatar = ContactMenuBar(self, host) if plugin_menu_context else Image()
+        self.states = HTML("")
         try:  # FIXME: dirty hack to force using an Image when the menu is actually empty
             self.avatar.items[0]
         except IndexError:
@@ -139,12 +140,14 @@
         self.updateAvatar(host.getAvatarURL(self.jid.bare))
         self.add(self.avatar)
         self.add(self.label)
+        self.add(self.states)
         self.addClickListener(self)
 
     def setAlert(self, alert):
-        """Show a visual indicator
+        """Show a visual indicator.
 
-        @param alert: True if alert indicator show be shown"""
+        @param alert (bool): True if alert indicator show be shown
+        """
         self.label.setAlert(alert)
 
     def updateAvatar(self, url):
@@ -159,7 +162,14 @@
 
         @param new_nick (unicode): new nickname to use
         """
-        self.label.updateNick(new_nick)
+        self.label.updateNick(html_tools.html_sanitize(new_nick))
+
+    def updateStates(self, states):
+        """Update the states.
+
+        @param states (dict{unicode: unicode}): new states
+        """
+        self.states.setHTML(u''.join(states.values()))
 
     def onClick(self, sender):
         try:
--- a/src/browser/sat_browser/editor_widget.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/editor_widget.py	Wed Mar 18 10:17:04 2015 +0100
@@ -75,7 +75,7 @@
 
     def _onComposing(self):
         """Callback when the user is composing a text."""
-        self.host.selected_widget.state_machine._onEvent("composing")
+        self.host.selected_widget.chat_state_machine._onEvent("composing")
 
     def onMouseUp(self, sender, x, y):
         size = (self.getOffsetWidth(), self.getOffsetHeight())
--- a/src/browser/sat_browser/game_radiocol.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/game_radiocol.py	Wed Mar 18 10:17:04 2015 +0100
@@ -44,6 +44,9 @@
 import dialog
 
 
+unicode = str # XXX: pyjama doesn't manage unicode
+
+
 class MetadataPanel(FlexTable):
 
     def __init__(self):
@@ -331,7 +334,7 @@
         def callback(room_jid, contacts):
             contacts = [unicode(contact) for contact in contacts]
             room_jid_s = unicode(room_jid) if room_jid else ''
-            host.bridge.RadioCollective(contacts, room_jid_s, profile=C.PROF_KEY_NONE)
+            host.bridge.launchRadioCollective(contacts, room_jid_s, profile=C.PROF_KEY_NONE)
         dialog.RoomAndContactsChooser(host, callback, ok_button="Choose", title="Collective Radio", visible=(False, True))
 
 
--- a/src/browser/sat_browser/game_tarot.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/game_tarot.py	Wed Mar 18 10:17:04 2015 +0100
@@ -48,6 +48,9 @@
 MIN_HEIGHT = 500
 
 
+unicode = str  # XXX: pyjama doesn't manage unicode
+
+
 class CardWidget(TarotCard, Image, MouseHandler):
     """This class is used to represent a card, graphically and logically"""
 
@@ -400,7 +403,6 @@
             host.bridge.launchTarotGame(other_players, room_jid_s, profile=C.PROF_KEY_NONE)
         dialog.RoomAndContactsChooser(host, onPlayersSelected, 3, title="Tarot", title_invite=_(u"Please select 3 other players"), visible=(False, True))
 
-
     def gotMenus():
         host.menus.addMenu(C.MENU_GLOBAL, (D_(u"Games"), D_(u"Tarot")), callback=onTarotGame)
     host.addListener('gotMenus', gotMenus)
--- a/src/browser/sat_browser/html_tools.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/html_tools.py	Wed Mar 18 10:17:04 2015 +0100
@@ -29,20 +29,55 @@
     """Naive sanitization of HTML"""
     return html.replace('<', '&lt;').replace('>', '&gt;')
 
+
 def html_strip(html):
     """Strip leading/trailing white spaces, HTML line breaks and &nbsp; sequences."""
     cleaned = re.sub(r"^(<br/?>|&nbsp;|\s)+", "", html)
     cleaned = re.sub(r"(<br/?>|&nbsp;|\s)+$", "", cleaned)
     return cleaned
 
+
 def inlineRoot(xhtml):
     """ make root element inline """
     doc = dom.parseString(xhtml)
     return xmltools.inlineRoot(doc)
 
+
 def convertNewLinesToXHTML(text):
     return text.replace('\n', '<br/>')
 
+
 def XHTML2Text(xhtml):
     """Helper method to apply both html_sanitize and convertNewLinesToXHTML"""
     return convertNewLinesToXHTML(html_sanitize(xhtml))
+
+
+def buildPresenceStyle(presence, base_style=None):
+    """Return the CSS classname to be used for displaying the given presence information.
+
+    @param presence (unicode): presence is a value in ('', 'chat', 'away', 'dnd', 'xa')
+    @param base_style (unicode): base classname
+    @return: unicode
+    """
+    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 (unicode): a value in ("", "chat", "away", "dnd", "xa").
+    @param base_style (unicode): 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
--- a/src/browser/sat_browser/main_panel.py	Thu Mar 19 20:41:46 2015 +0100
+++ b/src/browser/sat_browser/main_panel.py	Wed Mar 18 10:17:04 2015 +0100
@@ -42,7 +42,7 @@
 import base_menu
 import libervia_widget
 import editor_widget
-import contact_list
+import html_tools
 from constants import Const as C
 
 
@@ -171,7 +171,7 @@
         self.button = self.addCategory(u"◉")
         presence_menu = self.button.getSubMenu()
         for presence, presence_i18n in C.PRESENCE.items():
-            html = u'<span class="%s">◉</span> %s' % (contact_list.buildPresenceStyle(presence), presence_i18n)
+            html = u'<span class="%s">◉</span> %s' % (html_tools.buildPresenceStyle(presence), presence_i18n)
             presence_menu.addItem(html, True, base_menu.SimpleCmd(lambda presence=presence: self.changePresenceCb(presence)))
         self.parent_panel = parent
 
@@ -219,7 +219,7 @@
 
     def setPresence(self, presence):
         self._presence = presence
-        contact_list.setPresenceStyle(self.presence_bar.button, self._presence)
+        html_tools.setPresenceStyle(self.presence_bar.button, self._presence)
 
     def setStatus(self, status):
         self.status_panel.setContent({'text': status})