changeset 51:8c67ea98ab91

frontend improved to take into account new SàT features - quick_frontend: better use of contact management, it now manages nicks, avatars, and connected status - quick_frontend: disconnect and remove are now 2 separate methods for contact list - wix: new contact list using HTML items, and showing avatars. Groups are not showed for now - wix: contact status now use tuples, to keep order, human readable status and color of contact - wix: contact list is updated when avatar or nick is found - wix: fixed 'question' dialog, which is renamed in 'yes/no' - wix: action result are now ignored for unkwnown id - sortilege refactored to work again
author Goffi <goffi@goffi.org>
date Thu, 07 Jan 2010 00:17:27 +1100
parents daa1f01a5332
children 6455fb62ff83
files frontends/quick_frontend/quick_app.py frontends/quick_frontend/quick_chat_list.py frontends/quick_frontend/quick_contact_list.py frontends/quick_frontend/quick_contact_management.py frontends/sat_bridge_frontend/DBus.py frontends/sortilege/sortilege frontends/wix/main_window.py
diffstat 7 files changed, 286 insertions(+), 196 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/quick_frontend/quick_app.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/quick_frontend/quick_app.py	Thu Jan 07 00:17:27 2010 +1100
@@ -22,23 +22,23 @@
 from logging import debug, info, error
 from tools.jid  import JID
 from sat_bridge_frontend.DBus import DBusBridgeFrontend
-from quick_frontend.quick_contact_management import QuickContactManagement
-
+import pdb
 
 class QuickApp():
     """This class contain the main methods needed for the frontend"""
 
     def __init__(self):
         self.rosterList = {}
-        self.CM = QuickContactManagement()  #a short name if more handy
         
         ## bridge ##
         self.bridge=DBusBridgeFrontend()
         self.bridge.register("newContact", self.newContact)
         self.bridge.register("newMessage", self.newMessage)
         self.bridge.register("presenceUpdate", self.presenceUpdate)
+        self.bridge.register("subscribe", self.subscribe)
         self.bridge.register("paramUpdate", self.paramUpdate)
         self.bridge.register("contactDeleted", self.contactDeleted)
+        self.bridge.register("updatedValue", self.updatedValue, "request")
         self.bridge.register("askConfirmation", self.askConfirmation, "request")
         self.bridge.register("actionResult", self.actionResult, "request")
         self.bridge.register("actionResultExt", self.actionResult, "request")
@@ -59,13 +59,23 @@
         for contact in self.bridge.getContacts():
             self.newContact(contact[0], contact[1], contact[2])
 
-        for status in self.bridge.getPresenceStatus():
-            self.presenceUpdate(status[0], status[1], status[2], status[3], status[4])
+        presences = self.bridge.getPresenceStatus()
+        for contact in presences:
+            for res in presences[contact]:
+                jabber_id = contact+('/'+res if res else '')
+                show = presences[contact][res][0]
+                priority = presences[contact][res][1]
+                statuses = presences[contact][res][2]
+                self.presenceUpdate(jabber_id, show, priority, statuses)
+
+        waitingSub = self.bridge.getWaitingSub()
+        for sub in waitingSub:
+            self.subscribe(waitingSub[sub], sub)
 
 
     def newContact(self, JabberId, attributes, groups):
-        jid=JID(JabberId)
-        self.rosterList[jid.short]=(dict(attributes), list(groups))
+        entity=JID(JabberId)
+        self.rosterList[entity.short]=(dict(attributes), list(groups))
     
     def newMessage(self, from_jid, msg, type, to_jid):
         sender=JID(from_jid)
@@ -76,64 +86,66 @@
     def setStatusOnline(self, online=True):
         pass
 
-    def presenceUpdate(self, jabber_id, type, show, status, priority):
-        debug ("presence update for %s (type=%s, show=%s, status=%s)", jabber_id, type, show, status);
-        jid=JID(jabber_id)
-        debug ("jid.short=%s whoami.short=%s", jid.short, self.whoami.short)
+    def presenceUpdate(self, jabber_id, show, priority, statuses):
+        debug ("presence update for %s (show=%s, statuses=%s)", jabber_id, show, statuses);
+        from_jid=JID(jabber_id)
+        debug ("from_jid.short=%s whoami.short=%s", from_jid.short, self.whoami.short)
 
-        ### subscription management ###
-        if type=="subscribed":
-            # this is a subscription confirmation, we just have to inform user
-            self.showDialog("The contact %s has accepted your subscription" % jid.short, 'Subscription confirmation')
-            return
-        elif type=="unsubscribed":
-            # this is a subscription refusal, we just have to inform user
-            self.showDialog("The contact %s has refused your subscription" % jid.short, 'Subscription refusal', 'error')
-            return
-        elif type=="subscribe":
-            # this is a subscrition request, we have to ask for user confirmation
-            answer = self.showDialog("The contact %s wants to subscribe to your presence.\nDo you accept ?" % jid.short, 'Subscription confirmation', 'question')
-            if answer:
-                self.bridge.setPresence(type="subscribed", to=jid.short)
-            else:
-                self.bridge.setPresence(type="unsubscribed", to=jid.short)
-            return
-        ### subscription management end ###
-
-        if jid.short==self.whoami.short:
+        if from_jid.short==self.whoami.short:
             if not type:
                 self.setStatusOnline(True)
             elif type=="unavailable":
                 self.setStatusOnline(False)
             return
 
-        if not type:
+        if show != 'unavailable':
             name=""
             group=""
-            if self.rosterList.has_key(jid.short):
-                if self.rosterList[jid.short][0].has_key("name"):
-                    name=self.rosterList[jid.short][0]["name"]
-                if self.rosterList[jid.short][0].has_key("show"):
-                    name=self.rosterList[jid.short][0]["show"]
-                if self.rosterList[jid.short][0].has_key("status"):
-                    name=self.rosterList[jid.short][0]["status"]
-                if len(self.rosterList[jid.short][1]):
-                    group=self.rosterList[jid.short][1][0]
+            if self.rosterList.has_key(from_jid.short):
+                if self.rosterList[from_jid.short][0].has_key("name"):
+                    name=self.rosterList[from_jid.short][0]["name"]
+                if len(self.rosterList[from_jid.short][1]):
+                    group=self.rosterList[from_jid.short][1][0]
 
             #FIXME: must be moved in a plugin
-            if jid.short in self.watched and not jid.short in self.onlineContact:
-                self.showAlert("Watched jid [%s] is connected !" % jid.short)
+            if from_jid.short in self.watched and not from_jid.short in self.onlineContact:
+                self.showAlert("Watched jid [%s] is connected !" % from_jid.short)
 
-            self.onlineContact.add(jid)  #FIXME onlineContact is useless with CM, must be removed
-            self.CM.add(jid)
-            self.contactList.replace(jid, show=show, status=status, name=name, group=group)
+            self.onlineContact.add(from_jid)  #FIXME onlineContact is useless with CM, must be removed
+            self.CM.add(from_jid)
+            self.CM.update(from_jid, 'name', name)
+            self.CM.update(from_jid, 'show', show)
+            self.CM.update(from_jid, 'statuses', statuses)
+            self.CM.update(from_jid, 'group', group)
+            cache = self.bridge.getProfileCache(from_jid)
+            if cache.has_key('nick'): 
+                self.CM.update(from_jid, 'nick', cache['nick'])
+            if cache.has_key('avatar'): 
+                self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar']))
+            self.contactList.replace(from_jid)
 
+        if show=="unavailable" and from_jid in self.onlineContact:
+            self.onlineContact.remove(from_jid)
+            self.CM.remove(from_jid)
+            if not self.CM.isConnected(from_jid):
+                self.contactList.disconnect(from_jid)
 
-        if type=="unavailable" and jid in self.onlineContact:
-            self.onlineContact.remove(jid)
-            self.CM.remove(jid)
-            self.contactList.remove(jid)
-
+    def subscribe(self, type, raw_jid):
+        """Called when a subsciption maangement signal is received"""
+        entity = JID(raw_jid)
+        if type=="subscribed":
+            # this is a subscription confirmation, we just have to inform user
+            self.showDialog("The contact %s has accepted your subscription" % entity.short, 'Subscription confirmation')
+        elif type=="unsubscribed":
+            # this is a subscription refusal, we just have to inform user
+            self.showDialog("The contact %s has refused your subscription" % entity.short, 'Subscription refusal', 'error')
+        elif type=="subscribe":
+            # this is a subscriptionn request, we have to ask for user confirmation
+            answer = self.showDialog("The contact %s wants to subscribe to your presence.\nDo you accept ?" % entity.short, 'Subscription confirmation', 'yes/no')
+            if answer:
+                self.bridge.subscription("subscribed", entity.short)
+            else:
+                self.bridge.subscribed("unsubscribed", entity.short)
 
     def showDialog(self, message, title, type="info"):
         raise NotImplementedError
@@ -151,13 +163,26 @@
 
     def contactDeleted(self, jid):
         target = JID(jid)
+        self.CM.remove(target)
+        self.contactList.remove(self.CM.get_full(target))
         try:
             self.onlineContact.remove(target.short)
         except KeyError:
             pass
-        self.contactList.remove(self.CM.get_full(jid))
-        self.CM.remove(target)
-    
+
+    def updatedValue(self, name, data):
+        print "toto"
+        print "updatedValue", name, data
+        if name == "profile_nick":
+            target = JID(data['jid'])
+            self.CM.update(target, 'nick', data['nick'])
+            self.contactList.replace(target)
+        elif name == "profile_avatar":
+            target = JID(data['jid'])
+            filename = self.bridge.getAvatarFile(data['avatar'])
+            self.CM.update(target, 'avatar', filename)
+            self.contactList.replace(target)
+
     def askConfirmation(self, type, id, data):
         raise NotImplementedError
     
--- a/frontends/quick_frontend/quick_chat_list.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/quick_frontend/quick_chat_list.py	Thu Jan 07 00:17:27 2010 +1100
@@ -30,12 +30,12 @@
         dict.__init__(self)
         self.host = host
 
-    def __getitem__(self,name_param):
-        name=JID(name_param).short
-        if not self.has_key(name):
+    def __getitem__(self,to_jid):
+        target=JID(to_jid)
+        if not self.has_key(target.short):
             #we have to create the chat win
-            self[name] = self.createChat(name)
-        return dict.__getitem__(self,name)
+            self[target.short] = self.createChat(target)
+        return dict.__getitem__(self,target.short)
 
-    def createChat(self, name):
+    def createChat(self, target):
         raise NotImplementedError
--- a/frontends/quick_frontend/quick_contact_list.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/quick_frontend/quick_contact_list.py	Thu Jan 07 00:17:27 2010 +1100
@@ -24,19 +24,27 @@
 
 
 class QuickContactList():
+    """This class manage the visual representation of contacts"""
 
-    def __init__(self):
+    def __init__(self, CM):
+        """
+        @param CM: instance of QuickContactManagement
+        """
         debug("Contact List init")
-        self.jid_ids={}
+        self.CM = CM
     
-    def replace(self, jid, name="", show="", status="", group=""):
-        """add a contact to the list"""
+    def replace(self, jid):
+        """add a contact to the list if doesn't exist, else update it"""
+        raise NotImplementedError
+    
+    def disconnect(self, jid):
+        """mark a contact disconnected"""
         raise NotImplementedError
     
     def remove(self, jid):
         """remove a contact from the list"""
         raise NotImplementedError
     
-    def add(self, jid, name="", show="", status="", group=""):
+    def add(self, jid):
         """add a contact to the list"""
         raise NotImplementedError
--- a/frontends/quick_frontend/quick_contact_management.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/quick_frontend/quick_contact_management.py	Thu Jan 07 00:17:27 2010 +1100
@@ -19,29 +19,68 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-from logging import debug, info, error
+from logging import debug, info, warning, error
 from tools.jid  import JID
 import pdb
 
 
 class QuickContactManagement():
     """This helper class manage the contacts and ease the use of nicknames and shortcuts"""
+    ### FIXME: is SàT a better place for all this stuff ??? ###
 
     def __init__(self):
         self.__contactlist = {}
 
-    def add(self, jid_param):
-        jid=JID(jid_param)
-        self.__contactlist[jid.short] = {'name':jid, 'last_resource':jid.resource}
+    def add(self, entity):
+        """Add contact to the list, update resources"""
+        if not self.__contactlist.has_key(entity.short):
+            self.__contactlist[entity.short] = {'resources':[]}
+        if entity.resource in self.__contactlist[entity.short]['resources']:
+            self.__contactlist[entity.short]['resources'].remove(entity.resource)
+        self.__contactlist[entity.short]['resources'].append(entity.resource)
     
-    def remove(self, jid_param):
-        jid=JID(jid_param)
+    def getAttr(self, entity, name):
+        """Return a specific attribute of contact, or all attributes
+        @param entity: jid of the contact
+        @param name: name of the attribute
+        @return: asked attribute"""
+        if self.__contactlist.has_key(entity.short):
+            if name == 'status':  #FIXME: for the moment, we only use the first status
+                if self.__contactlist[entity.short]['statuses']:
+                    return self.__contactlist[entity.short]['statuses'].values()[0]
+            if self.__contactlist[entity.short].has_key(name):
+                return self.__contactlist[entity.short][name]
+        else:
+            debug('Trying to get attribute for an unknown contact')
+        return None
+    
+    def isConnected(self, entity):
+        """Tell if the contact is online"""
+        print "is connected (%s): %s" %(entity, str(self.__contactlist.has_key(entity.short))) #gof
+        return self.__contactlist.has_key(entity.short)
+
+    def remove(self, entity):
+        """remove resource. If no more resource is online or is no resource is specified, contact is deleted"""
         try:
-            del self.__contactlist[jid.short]
+            if entity.resource:
+                self.__contactlist[entity.short]['resources'].remove(entity.resource)
+            if not entity.resource or not self.__contactlist[entity.short]['resources']:
+                #no more resource available: the contact seems really disconnected
+                del self.__contactlist[entity.short]
         except KeyError:
             pass
 
-    def get_full(self, jid_param):
-        jid=JID(jid_param)
-        return jid.short+'/'+self.__contactlist[jid.short]['last_resource']
+    def update(self, entity, key, value):
+        """Update attribute of contact
+        @param entity: jid of the contact
+        @param key: name of the attribute
+        @param value: value of the attribute
+        """
+        if self.__contactlist.has_key(entity.short):
+            self.__contactlist[entity.short][key] = value
+        else:
+            debug ('Trying to update an uknown contact: %s', entity.short)
+
+    def get_full(self, entity):
+        return entity.short+'/'+self.__contactlist[entity.short]['resources'][-1]
         
--- a/frontends/sat_bridge_frontend/DBus.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/sat_bridge_frontend/DBus.py	Thu Jan 07 00:17:27 2010 +1100
@@ -52,6 +52,9 @@
     def getPresenceStatus(self):
         return self.db_comm_iface.getPresenceStatus()
 
+    def getWaitingSub(self):
+        return self.db_comm_iface.getWaitingSub()
+
     def sendMessage(self, to, message):
         return self.db_comm_iface.sendMessage(to, message)
     
@@ -64,14 +67,20 @@
     def getProfile(self, target):
         return self.db_comm_iface.getProfile(target)
 
+    def getProfileCache(self, target):
+        return self.db_comm_iface.getProfileCache(target)
+
     def getAvatarFile(self, hash):
         return self.db_comm_iface.getAvatarFile(hash)
 
     def in_band_register(self, target):
         return self.db_comm_iface.in_band_register(target)
     
-    def setPresence(self, to="", type="", show="", status="", priority=0):
-        return self.db_comm_iface.setPresence(to, type, show, status, priority)
+    def setPresence(self, to="", show="", priority=0, statuses={}):
+        return self.db_comm_iface.setPresence(to, show, priority, statuses)
+
+    def subscription(self, type, entity):
+        return self.db_comm_iface.subscription(type, entity)
 
     def setParam(self, name, value, category):
         return self.db_comm_iface.setParam(name, value, category)
--- a/frontends/sortilege/sortilege	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/sortilege/sortilege	Thu Jan 07 00:17:27 2010 +1100
@@ -43,6 +43,7 @@
 from quick_frontend.quick_chat_list import QuickChatList
 from quick_frontend.quick_contact_list import QuickContactList
 from quick_frontend.quick_app import QuickApp
+from quick_frontend.quick_contact_management import QuickContactManagement
 
 ### logging configuration FIXME: put this elsewhere ###
 logging.basicConfig(level=logging.CRITICAL,  #TODO: configure it top put messages in a log file
@@ -80,8 +81,10 @@
 
 class ContactList(Window, QuickContactList):
     
-    def __init__(self):
-        QuickContactList.__init__(self)
+    def __init__(self, host, CM):
+        QuickContactList.__init__(self, CM)
+        self.host = host
+        self.jid_list = []
         self.__index=0  #indicate which contact is selected (ie: where we are)
         Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, "Contact List", code=code)
 
@@ -98,14 +101,15 @@
     def registerEnterCB(self, CB):
         self.__enterCB=CB
 
-    def replace(self, jid, name="", show="", status="", group=""):
+    def replace(self, jid):
         """add a contact to the list"""
-        self.jid_ids[jid] = name or jid
+        name = self.CM.getAttr(jid,'name')
+        self.jid_list.append(jid.short)
         self.update()
 
     def indexUp(self):
         """increment select contact index"""
-        if self.__index < len(self.jid_ids)-1:  #we dont want to select a missing contact
+        if self.__index < len(self.jid_list)-1:  #we dont want to select a missing contact
             self.__index = self.__index + 1
         self.update()
     
@@ -115,11 +119,15 @@
             self.__index = self.__index - 1
         self.update()
 
+    def disconnect(self, jid):
+        """for now, we just remove the contact"""
+        self.remove(jid)
+
     def remove(self, jid):
         """remove a contact from the list"""
-        del self.jid_ids[jid]
-        if self.__index >= len(self.jid_ids) and self.__index > 0:  #if select index is out of border, we put it on the last contact
-            self.__index = len(self.jid_ids)-1
+        self.jid_list.remove(jid.short)
+        if self.__index >= len(self.jid_list) and self.__index > 0:  #if select index is out of border, we put it on the last contact
+            self.__index = len(self.jid_list)-1
         self.update()
 
     def update(self):
@@ -127,13 +135,10 @@
         if self.isHidden():
             return
         Window.update(self)
-        viewList=[]
-        for jid in self.jid_ids:
-            viewList.append(self.jid_ids[jid])
-        viewList.sort()
+        self.jid_list.sort()
         begin=0 if self.__index<self.rHeight else self.__index-self.rHeight+1 
         idx=0
-        for item in viewList[begin:self.rHeight+begin]:
+        for item in self.jid_list[begin:self.rHeight+begin]:
             attr = curses.A_REVERSE if ( self.isActive() and (idx+begin) == self.__index ) else 0
             centered = item.center(self.rWidth) ## it's nicer in the center :)
             self.addYXStr(idx, 0, centered, attr)
@@ -147,10 +152,10 @@
         elif k == curses.KEY_DOWN:
             self.indexUp()
         elif k == ascii.NL:
-            if not self.jid_ids:
+            if not self.jid_list:
                 return
             try:
-                self.__enterCB(self.jid_ids.keys()[self.__index])
+                self.__enterCB(self.jid_list[self.__index])
             except NameError:
                 pass # TODO: thrown an error here
             
@@ -170,6 +175,7 @@
         gobject.io_add_watch(0, gobject.IO_IN, self.loopCB)
 
         ## misc init stuff ##
+        self.CM = QuickContactManagement()
         self.listWins=[]
         self.chatParams={'timestamp':True,
                          'color':True,
@@ -190,7 +196,7 @@
         self.color(True)
 
         ## windows ##
-        self.contactList = ContactList()
+        self.contactList = ContactList(self, self.CM)
         self.editBar = EditBox(stdscr, "> ", self.code)
         self.editBar.activate(False)
         self.statusBar = StatusBar(stdscr, self.code)
@@ -237,10 +243,10 @@
     def showChat(self, chat):
         debug ("show chat")
         if self.currentChat:
-            debug ("hide de %s", self.currentChat)
+            debug ("hiding %s", self.currentChat)
             self.chat_wins[self.currentChat].hide()
         self.currentChat=chat
-        debug ("show de %s", self.currentChat)
+        debug ("showing %s", self.currentChat)
         self.chat_wins[self.currentChat].show()
         self.chat_wins[self.currentChat].update()
         
@@ -268,8 +274,8 @@
         pass
 
 
-    def presenceUpdate(self, jabber_id, type, show, status, priority):
-        QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority)
+    def presenceUpdate(self, jabber_id, show, priority, statuses):
+        QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses)
         self.editBar.replace_cur()
         curses.doupdate()
 
--- a/frontends/wix/main_window.py	Thu Jan 07 00:05:15 2010 +1100
+++ b/frontends/wix/main_window.py	Thu Jan 07 00:17:27 2010 +1100
@@ -34,24 +34,28 @@
 from quick_frontend.quick_chat_list import QuickChatList
 from quick_frontend.quick_contact_list import QuickContactList
 from quick_frontend.quick_app import QuickApp
+from quick_frontend.quick_contact_management import QuickContactManagement
+from cgi import escape
+import sys
 
+IMAGE_DIR = sys.path[0]+'/images'
 
 msgOFFLINE          = "offline"
 msgONLINE           = "online"
-idCONNECT           = 1
-idDISCONNECT        = 2
-idEXIT              = 3
-idPARAM             = 4
-idADD_CONTACT       = 5
-idREMOVE_CONTACT    = 6
-idSHOW_PROFILE      = 7
-idFIND_GATEWAYS     = 8
+idCONNECT,\
+idDISCONNECT,\
+idEXIT,\
+idPARAM,\
+idADD_CONTACT,\
+idREMOVE_CONTACT,\
+idSHOW_PROFILE,\
+idFIND_GATEWAYS = range(8)
 const_DEFAULT_GROUP = "Unclassed"
-const_STATUS        = {"Online":"",
-                      "Want to discuss":"chat",
-                      "AFK":"away",
-                      "Do Not Disturb":"dnd",
-                      "Away":"xa"}
+const_STATUS        = [("", "Online", None),
+                       ("chat", "Free for chat", "green"),
+                       ("away", "AFK", "brown"),
+                       ("dnd", "DND", "red"),
+                       ("xa", "Away", "red")]
 
 class ChatList(QuickChatList):
     """This class manage the list of chat windows"""
@@ -59,100 +63,96 @@
     def __init__(self, host):
         QuickChatList.__init__(self, host)
     
-    def createChat(self, name):
-        return Chat(name, self.host)
+    def createChat(self, target):
+        return Chat(target, self.host)
     
 
-
-class ContactList(wx.TreeCtrl, QuickContactList):
+class ContactList(wx.SimpleHtmlListBox, QuickContactList):
     """Customized control to manage contacts."""
 
-    def __init__(self, parent):
-        wx.TreeCtrl.__init__(self, parent, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS)
-        QuickContactList.__init__(self)
-        self.jid_ids={}
-        self.groups={}
-        self.root=self.AddRoot("")
-        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.onActivated, self)
+    def __init__(self, parent, CM):
+        wx.SimpleHtmlListBox.__init__(self, parent, -1)
+        QuickContactList.__init__(self, CM)
+        self.host = parent
+        self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated)
 
-        #icons
-        isz = (16,16)
-        il = wx.ImageList(isz[0], isz[1])
-        self.icon_online = il.Add(wx.ArtProvider_GetBitmap(wx.ART_TICK_MARK, wx.ART_OTHER, isz))
-        self.icon_unavailable = il.Add(wx.ArtProvider_GetBitmap(wx.ART_CROSS_MARK, wx.ART_OTHER, isz))
-        self.AssignImageList(il)
+    def __find_idx(self, jid, reverse=False):
+        """Find indexes of given jid in contact list
+        @return: list of indexes"""
+        result=[]
+        for i in range(self.GetCount()):
+            if self.GetClientData(i).short == jid.short:
+                result.append(i)
+        return result
 
-        self.__addNode(const_DEFAULT_GROUP)
+    def replace(self, jid):
+        debug("update %s" % jid)
+        if not self.__find_idx(jid):
+            self.add(jid)
+        else:
+            for i in self.__find_idx(jid):
+                self.SetString(i, self.__presentItem(jid))
 
-    def __addNode(self, label):
-        """Add an item container"""
-        ret=self.AppendItem(self.root, label)
-        self.SetPyData(ret, "[node]")
-        self.SetItemBold(ret)
-        self.groups[label]=ret
+    def disconnect(self, jid):
+        self.remove(jid) #for now, we only show online contacts
 
-    def replace(self, jid, name="", show="", status="", group=""):
-        debug("status = %s show = %s",status, show)
-        if not self.jid_ids.has_key(jid):
-            self.add(jid, name, show, status, group)
-        else:
-            debug ("updating %s",jid)
-            self.__presentItem(jid, name, show, status, group)
-
-    def __presentItem(self, jid, name, show, status, group):
+    def __presentItem(self, jid):
         """Make a nice presentation of the contact in the list."""
-        id=self.jid_ids[jid]
-        label= "%s [%s] \n %s" % ((name or jid), (show or "online"), status)
-        self.SetItemText(id, label)
+        name = self.CM.getAttr(jid,'name')
+        nick = self.CM.getAttr(jid,'nick')
+        show =  filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0]
+        #show[0]==shortcut
+        #show[1]==human readable
+        #show[2]==color (or None)
+        show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
+        status = self.CM.getAttr(jid,'status') or ''
+        avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png'
+        
+        #XXX: yes table I know :) but wxHTML* doesn't support CSS
+        html = """
+        <table border='0'>
+        <td>
+            <img  height='64' width='64' src='%s' />
+        </td>
+        <td>
+            <b>%s</b> %s<br />
+            <i>%s</i>
+        </td>
+        </table>
+        """ % (avatar,
+               escape(nick or name or jid.node or jid.short),
+               show_html,
+               escape(status)) 
 
-        # icon
-        if not show or show=="chat":
-            self.SetItemImage(id, self.icon_online)
-        else:
-            self.SetItemImage(id, self.icon_unavailable)
+        return html
 
-        #colour
-        if not show:
-            self.SetItemTextColour(id, wx.BLACK)
-        elif show=="chat":
-            self.SetItemTextColour(id, wx.GREEN)
-        elif show=="away":
-            self.SetItemTextColour(id, wx.BLUE)
-        else:
-            self.SetItemTextColour(id, wx.RED)
-
-    def add(self, jid, name="", show="", status="", group=""):
+    def add(self, jid):
         """add a contact to the list"""
         debug ("adding %s",jid)
-        dest_group=group or const_DEFAULT_GROUP
-        if not self.groups.has_key(dest_group):
-            self.__addNode(dest_group)
-        self.jid_ids[jid]=self.AppendItem(self.groups[dest_group], "")
-        self.__presentItem(jid, name, show, status, group)
-        self.SetPyData(self.jid_ids[jid], "[contact]"+jid)
-        self.EnsureVisible(self.jid_ids[jid])
-        self.Refresh() #FIXME: Best way ?
+        idx = self.Append(self.__presentItem(jid))
+
+        self.SetClientData(idx, jid)
 
     def remove(self, jid):
         """remove a contact from the list"""
         debug ("removing %s",jid)
-        self.Delete(self.jid_ids[jid])
-        del self.jid_ids[jid]
-        self.Refresh() #FIXME: Best way ?
+        list_idx = self.__find_idx(jid)
+        list_idx.reverse()  #we me make some deletions, we have to reverse the order
+        for i in list_idx:
+            self.Delete(i)
 
     def onActivated(self, event):
         """Called when a contact is clicked or activated with keyboard."""
-        if self.GetPyData(event.GetItem()).startswith("[contact]"):
-            self.onActivatedCB(self.GetPyData(event.GetItem())[9:])
-        else:
-            event.Skip()
+        data = self.getSelection()
+        self.onActivatedCB(data)
+        event.Skip()
 
     def getSelection(self):
         """Return the selected contact, or an empty string if there is not"""
-        data = self.GetPyData(self.GetSelection())
-        if not data or not data.startswith("[contact]"):
-            return ""
-        return JID(data[9:])
+        if self.GetSelection() == wx.NOT_FOUND:
+            return ""  #FIXME: gof: à améliorer
+        data = self.GetClientData(self.GetSelection())
+        return data
 
     def registerActivatedCB(self, cb):
         """Register a callback with manage contact activation."""
@@ -163,10 +163,11 @@
 
     def __init__(self):
         wx.Frame.__init__(self,None, title="SAT Wix", size=(400,200))
+        self.CM = QuickContactManagement() #gof:
 
 
         #Frame elements
-        self.contactList = ContactList(self)
+        self.contactList = ContactList(self, self.CM)
         self.contactList.registerActivatedCB(self.onContactActivated)
         self.chat_wins=ChatList(self)
         self.CreateStatusBar()
@@ -175,7 +176,7 @@
 
         #ToolBar
         self.tools=self.CreateToolBar()
-        self.statusBox =  wx.ComboBox(self.tools, -1, "Online", choices=const_STATUS.keys(),
+        self.statusBox =  wx.ComboBox(self.tools, -1, "Online", choices=[status[1] for status in const_STATUS],
                                       style=wx.CB_DROPDOWN | wx.CB_READONLY)
         self.tools.AddControl(self.statusBox)
         self.tools.AddSeparator()
@@ -186,7 +187,7 @@
         self.tools.Disable()
         
         #tray icon
-        ticon = wx.Icon("images/tray_icon.xpm", wx.BITMAP_TYPE_XPM)
+        ticon = wx.Icon(IMAGE_DIR+'/crystal/tray_icon.xpm', wx.BITMAP_TYPE_XPM)
         self.tray_icon = wx.TaskBarIcon()
         self.tray_icon.SetIcon(ticon, "Wix jabber client")
         wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick)
@@ -252,10 +253,11 @@
             flags = wx.OK | wx.ICON_INFORMATION
         elif type == 'error':
             flags = wx.OK | wx.ICON_ERROR
-        elif type == 'question':
-            flags = wx.OK | wx.ICON_QUESTION
+        elif type == 'yes/no':
+            flags = wx.YES_NO | wx.ICON_QUESTION
         else:
             flags = wx.OK | wx.ICON_INFORMATION
+            error('unmanaged dialog type: %s', type)
         dlg = wx.MessageDialog(self, message, title, flags)
         answer = dlg.ShowModal()
         dlg.Destroy()
@@ -272,8 +274,8 @@
         return
 
 
-    def presenceUpdate(self, jabber_id, type, show, status, priority):
-        QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority)
+    def presenceUpdate(self, jabber_id, show, priority, statuses):
+        QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses)
 
     def askConfirmation(self, type, id, data):
         #TODO: refactor this in QuickApp
@@ -315,6 +317,9 @@
 
     def actionResult(self, type, id, data):
         debug ("actionResult: type = [%s] id = [%s] data = [%s]" % (type, id, data))
+        if not id in self.current_action_ids:
+            debug ('unknown id, ignoring')
+            return
         if type == "SUPPRESS":
             self.current_action_ids.remove(id)
         elif type == "SUCCESS":
@@ -349,7 +354,6 @@
                 callback = self.current_action_ids_cb[id]
                 del self.current_action_ids_cb[id]
                 callback(data)
-            print ("Dict of dict found as result")
         else:
             error ("FIXME FIXME FIXME: type [%s] not implemented" % type)
             raise NotImplementedError
@@ -384,10 +388,10 @@
 
     def onContactActivated(self, jid):
         debug ("onContactActivated: %s", jid)
-        if self.chat_wins[jid].IsShown():
-            self.chat_wins[jid].Hide()
+        if self.chat_wins[jid.short].IsShown():
+            self.chat_wins[jid.short].Hide()
         else:
-            self.chat_wins[jid].Show()
+            self.chat_wins[jid.short].Show()
 
     def onConnectRequest(self, e):
         self.bridge.connect()
@@ -396,9 +400,9 @@
         self.bridge.disconnect()
 
     def __updateStatus(self):
-        show = const_STATUS[self.statusBox.GetValue()]
+        show = filter(lambda x:x[1] == self.statusBox.GetValue(), const_STATUS)[0][0]
         status =  self.statusTxt.GetValue()
-        self.bridge.setPresence(show=show, status=status)
+        self.bridge.setPresence(show=show, statuses={'default':status})  #FIXME: manage multilingual statuses
 
     def onStatusChange(self, e):
         debug("Status change request")
@@ -445,7 +449,7 @@
                               )
 
         if dlg.ShowModal() == wx.ID_YES:
-            info("Unsubsribing %s presence", target.short)
+            info("Unsubscribing %s presence", target.short)
             self.bridge.delContact(target.short)
 
         dlg.Destroy()
@@ -476,7 +480,6 @@
         id = self.bridge.findGateways(self.whoami.domain)
         self.current_action_ids.add(id)
         self.current_action_ids_cb[id] = self.onGatewaysFound
-        print "Find Gateways id=", id
 
     def onGatewaysFound(self, data):
         """Called when SàT has found the server gateways"""