changeset 125:8d611eb9ae48

primitivus: contact list enhancement - primitivus: contact list now display groups and sexy name (instead of bare jid) - primitivus: contact list now show an alert when somebody want to contact you, and its chat window is not on the screen - primitivus: cursor is now visible when going to chat window (useful to go back in logs)
author Goffi <goffi@goffi.org>
date Mon, 12 Jul 2010 17:50:00 +0800
parents 961e0898271f
children 1b33b681ebd8
files frontends/primitivus/chat.py frontends/primitivus/contact_list.py frontends/primitivus/custom_widgets.py frontends/primitivus/primitivus frontends/quick_frontend/quick_app.py frontends/wix/contact_list.py
diffstat 6 files changed, 180 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/primitivus/chat.py	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/primitivus/chat.py	Mon Jul 12 17:50:00 2010 +0800
@@ -48,7 +48,14 @@
         return self.display_widget(size, focus).rows(size, focus)
 
     def render(self, size, focus=False):
-        return self.display_widget(size, focus).render(size, focus)
+        canvas = urwid.CompositeCanvas(self.display_widget(size, focus).render(size, focus))
+        if focus:
+            canvas.set_cursor(self.get_cursor_coords(size))
+        return canvas
+
+    def get_cursor_coords(self, size):
+        #(maxcol,) = size
+        return 0, 0
 
     def display_widget(self, size, focus):
         my_mess = (self.from_jid.resource == self.parent.nick) if self.parent.type == "group" else (self.from_jid.short == self.my_jid.short) #mymess = True if message comes from local user
@@ -134,7 +141,7 @@
 
 
     def __buildPresentList(self):
-        self.present_wid = custom_widgets.GenericList([],option_type = custom_widgets.UnselectableText)
+        self.present_wid = custom_widgets.GenericList([],option_type = custom_widgets.ClickableText)
         return self.present_wid
    
     def __appendPresentPanel(self):
--- a/frontends/primitivus/contact_list.py	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/primitivus/contact_list.py	Mon Jul 12 17:50:00 2010 +0800
@@ -21,6 +21,7 @@
 
 import urwid
 from quick_frontend.quick_contact_list import QuickContactList
+from tools.jid import JID
 import custom_widgets
 
 
@@ -29,13 +30,13 @@
 
     def __init__(self, host, CM, on_click=None, on_change=None, user_data=None):
         self.host = host
-               
-        self.list_wid = custom_widgets.GenericList([], style=['single','no_first_select'], align='left', on_click=self.__contactClicked, on_change=on_change)
-
+        self.selected = None
+        self.groups={}
+        self.alert_jid=set()
+        
         #we now build the widget
-        frame_body = self.list_wid
-        frame = urwid.Frame(frame_body)
-        self.main_widget = custom_widgets.LabelLine(frame, custom_widgets.SurroundedText(_("Contacts")))
+        self.frame = urwid.Frame(self.__buildList())
+        self.main_widget = custom_widgets.LabelLine(self.frame, custom_widgets.SurroundedText(_("Contacts")))
         urwid.WidgetWrap.__init__(self, self.main_widget)
         if on_click:
             urwid.connect_signal(self, 'click', on_click, user_data)
@@ -44,38 +45,126 @@
         QuickContactList.__init__(self, CM)
 
     def __contains__(self, jid):
-        contacts = self.list_wid.getAllValues()
-        return jid.short in contacts
+        for group in self.groups:
+            if jid.short in self.groups[group][1]:
+                return True
+        return False
+
+    def setFocus(self, name):
+        """give focus to the first group or contact with the given name"""
+        idx = 0
+        for widget in self.frame.body.body:
+            if widget.getValue() == name:
+                self.frame.body.set_focus(idx)
+                return
+            idx+=1
+
+    def putAlert(self, jid):
+        """Put an alert on the jid to get attention from user (e.g. for new message)"""
+        self.alert_jid.add(jid.short)
+        self.frame.body = self.__buildList()
+        self.host.redraw()
+
+    def __groupClicked(self, group_wid):
+        group = self.groups[group_wid.getValue()]
+        group[0] = not group[0]
+        self.frame.body = self.__buildList()
+        self.host.redraw()
+        self.setFocus(group_wid.getValue())
 
-    def __contactClicked(self, list_wid):
+    def __contactClicked(self, contact_wid, selected):
+        self.selected = contact_wid.data
+        for widget in self.frame.body.body:
+            if widget.__class__ == custom_widgets.SelectableText:
+                widget.setState(widget.data == self.selected, invisible=True)
+        if self.selected in self.alert_jid:
+            self.alert_jid.remove(self.selected)
+            self.frame.body = self.__buildList()
+            self.host.redraw()
         self._emit('click')
 
+    def __buildContact(self, content, param_contacts):
+        """Add contact representation in widget list
+        @param content: widget list, e.g. SimpleListWalker
+        @param contacts: list of JID"""
+        contacts = list(param_contacts)
+        contacts.sort()
+        for contact in contacts:
+            jid=JID(contact) 
+            name = self.CM.getAttr(jid,'name')
+            nick = self.CM.getAttr(jid,'nick')
+            display = nick or name or jid.node or jid.short
+            header = '(*) ' if contact in self.alert_jid else ''
+            widget = custom_widgets.SelectableText(display, selected = contact==self.selected, header=header, data=contact)
+            if contact in self.alert_jid:
+                widget.setAttribute('default','alert')
+            content.append(widget)
+            urwid.connect_signal(widget, 'change', self.__contactClicked)
+
+    def __buildList(self):
+        """Build the main contact list widget"""
+        content = urwid.SimpleListWalker([])
+        group_keys = self.groups.keys()
+        group_keys.sort()
+        for key in group_keys:
+            unfolded = self.groups[key][0]
+            if key!=None:
+                header = '[-]' if unfolded else '[+]'
+                widget = custom_widgets.ClickableText(key,header=header+' ')
+                content.append(widget)
+                urwid.connect_signal(widget, 'click', self.__groupClicked)
+            if unfolded:
+                self.__buildContact(content, self.groups[key][1])
+        return urwid.ListBox(content)
+
     def get_contact(self):
         """Return contact currently selected"""
-        return self.list_wid.getSelectedValue()
+        return self.selected
             
     def clear_contacts(self):
         """clear all the contact list"""
-        self.list_wid.changeValues([])
+        self.groups={}
 
-    def replace(self, jid, groups=None):
+    def replace(self, jid, groups=[None]):
         """add a contact to the list if doesn't exist, else update it"""
-        contacts = self.list_wid.getAllValues()
+        assert groups.__class__ == list
+        assert jid.__class__ == JID
+        if not groups:
+            groups=[None]
+        for group in groups:
+            if not self.groups.has_key(group):
+                self.groups[group] = [True,set()]  #[unfold,list_of_contacts]
+            self.groups[group][1].add(jid.short)
+        self.frame.body = self.__buildList()
+        self.host.redraw()
+
+
+        """contacts = self.list_wid.getAllValues()
         if jid.short not in contacts:
             contacts.append(jid.short)
             contacts.sort()
             self.list_wid.changeValues(contacts)
-            self._emit('change')
+            self._emit('change')"""
     
     def disconnect(self, jid):
         """mark a contact disconnected"""
-        self.remove(jid)
+        self.remove(jid.short)
     
     def remove(self, jid):
         """remove a contact from the list"""
-        self.list_wid.deleteValue(jid.short)
+        groups_to_remove = []
+        for group in self.groups:
+            contacts = self.groups[group][1]
+            if jid.short in contacts:
+                contacts.remove(jid.short)
+                if not len(contacts):
+                    groups_to_remove.append(group)
+        for group in groups_to_remove:
+            del self.groups[group]
+        self.frame.body = self.__buildList()
+        self.host.redraw()
     
-    def add(self, jid, param_groups=None):
+    def add(self, jid, param_groups=[None]):
         """add a contact to the list"""
-        self.replace(jid)
+        self.replace(jid,param_groups)
 
--- a/frontends/primitivus/custom_widgets.py	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/primitivus/custom_widgets.py	Mon Jul 12 17:50:00 2010 +0800
@@ -95,13 +95,31 @@
     """Text which can be selected with space"""
     signals = ['change']
     
-    def __init__(self, text, align='left'):
+    def __init__(self, text, align='left', header='', select_attr=None, default_attr=None, selected = False, data=None):
         self.text=unicode(text)
+        self.header=header
+        if data:
+            self.data=data
+        if select_attr:
+            self.selected = select_attr
+        if default_attr:
+            self.default = default_attr
         self.align = align
-        self.__selected=False
+        self.__selected=selected
 
     def getValue(self):
         return self.text
+
+    def setAttribute(self, name, value):
+        """Change attribut used for rendering widget
+        @param name: one of
+            -default: when not selected
+            -selected: when selected
+        @param value: name of the attribute
+            /!\ the attribute name followed by _focus is used when widget has focus"""
+        assert name in ['default', 'selected']
+        self.__setattr__(name,value)
+        self._invalidate()
     
     def setState(self, selected, invisible=False):
         """Change state
@@ -132,15 +150,24 @@
         return self.display_widget(size, focus).render(size, focus)
 
     def display_widget(self, size, focus):
-        attr = 'selected' if self.__selected else 'default'
+        try:
+            select_attr = self.selected
+        except AttributeError:
+            select_attr = 'selected'
+        try:
+            default_attr = self.default
+        except AttributeError:
+            default_attr = 'default'
+        attr = select_attr if self.__selected else default_attr
         if focus:
             attr+="_focus"
-        return urwid.Text((attr,self.text), align=self.align)
+        return urwid.Text((attr,self.header+self.text), align=self.align)
 
-class UnselectableText(SelectableText):
+class ClickableText(SelectableText):
+    signals = SelectableText.signals + ['click']
     
     def setState(self, selected, invisible=False):
-        pass
+        self._emit('click')
 
 class GenericList(urwid.WidgetWrap):
     signals = ['click','change']
--- a/frontends/primitivus/primitivus	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/primitivus/primitivus	Mon Jul 12 17:50:00 2010 +0800
@@ -60,6 +60,8 @@
                  ('selected_focus', 'default,bold', 'dark red'),
                  ('default', 'default', 'default'),
                  ('default_focus', 'default,bold', 'default'),
+                 ('alert', 'default,underline', 'default'),
+                 ('alert_focus', 'default,bold,underline', 'default'),
                  ('date', 'light gray', 'default'),
                  ('my_nick', 'dark red,bold', 'default'),
                  ('other_nick', 'dark cyan,bold', 'default'),
@@ -125,8 +127,6 @@
                 #The main widget is not built (probably in Profile Manager)
                 pass
 
-
-  
     def __buildMainWidget(self):
         self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw())
         #self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))])
@@ -166,6 +166,14 @@
                                     profile_key=self.profile)
             editBar.set_edit_text('')
 
+    def newMessage(self, from_jid, msg, type, to_jid, profile):
+        if not self.check_profile(profile):
+            return
+        QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
+        sender = JID(from_jid)
+        if JID(self.contactList.selected).short != sender.short:
+            self.contactList.putAlert(sender)
+
     def onJoinRoom(self, button, edit):
         self.removePopUp()
         room_jid = JID(edit.get_edit_text())
--- a/frontends/quick_frontend/quick_app.py	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/quick_frontend/quick_app.py	Mon Jul 12 17:50:00 2010 +0800
@@ -67,7 +67,7 @@
         self.current_action_ids = set()
         self.current_action_ids_cb = {}
     
-    def __check_profile(self, profile):
+    def check_profile(self, profile):
         """Tell if the profile is currently followed by the application"""
         return profile in self.profiles.keys()
 
@@ -160,7 +160,7 @@
 
     def connected(self, profile):
         """called when the connection is made"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug(_("Connected"))
         self.setStatusOnline(True)
@@ -169,7 +169,7 @@
 
     def disconnected(self, profile):
         """called when the connection is closed"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug(_("Disconnected"))
         self.CM.clear()
@@ -177,13 +177,13 @@
         self.setStatusOnline(False)
     
     def newContact(self, JabberId, attributes, groups, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         entity=JID(JabberId)
         self.rosterList[entity.short]=(dict(attributes), list(groups))
     
     def newMessage(self, from_jid, msg, type, to_jid, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         sender=JID(from_jid)
         addr=JID(to_jid)
@@ -196,7 +196,7 @@
         pass
 
     def presenceUpdate(self, jabber_id, show, priority, statuses, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("presence update for %(jid)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") % {'jid':jabber_id, 'show':show, 'priority':priority, 'statuses':statuses, 'profile':profile});
         from_jid=JID(jabber_id)
@@ -242,7 +242,7 @@
     
     def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
         """Called when a MUC room is joined"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Room [%(room_name)s] joined by %(profile)s, users presents:%(users)s") % {'room_name':room_id+'@'+room_service, 'profile': profile, 'users':room_nicks})
         room_jid=room_id+'@'+room_service
@@ -254,7 +254,7 @@
 
     def roomUserJoined(self, room_id, room_service, user_nick, user_data, profile):
         """Called when an user joined a MUC room"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         room_jid=room_id+'@'+room_service
         if self.chat_wins.has_key(room_jid):
@@ -263,7 +263,7 @@
 
     def roomUserLeft(self, room_id, room_service, user_nick, user_data, profile):
         """Called when an user joined a MUC room"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         room_jid=room_id+'@'+room_service
         if self.chat_wins.has_key(room_jid):
@@ -272,7 +272,7 @@
 
     def roomNewSubject(self, room_id, room_service, subject, profile):
         """Called when subject of MUC room change"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         room_jid=room_id+'@'+room_service
         if self.chat_wins.has_key(room_jid):
@@ -280,7 +280,7 @@
             debug (_("new subject for room [%(room_jid)s]: %(subject)s") % {'room_jid':room_jid, "subject":subject})
     
     def tarotGameStarted(self, room_jid, referee, players, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug  (_("Tarot Game Started \o/"))
         if self.chat_wins.has_key(room_jid):
@@ -288,7 +288,7 @@
             debug (_("new Tarot game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee':referee, 'room_jid':room_jid, 'players':[str(player) for player in players]})
        
     def tarotGameNew(self, room_jid, hand, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("New Tarot Game"))
         if self.chat_wins.has_key(room_jid):
@@ -296,21 +296,21 @@
 
     def tarotChooseContrat(self, room_jid, xml_data, profile):
         """Called when the player has too select his contrat"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Tarot: need to select a contrat"))
         if self.chat_wins.has_key(room_jid):
             self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data)
 
     def tarotShowCards(self, room_jid, game_stage, cards, data, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Show cards"))
         if self.chat_wins.has_key(room_jid):
             self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data)
 
     def tarotMyTurn(self, room_jid, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("My turn to play"))
         if self.chat_wins.has_key(room_jid):
@@ -318,21 +318,21 @@
     
     def tarotScore(self, room_jid, xml_data, winners, loosers, profile): 
         """Called when the game is finished and the score are updated"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Tarot: score received"))
         if self.chat_wins.has_key(room_jid):
             self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers)
 
     def tarotCardsPlayed(self, room_jid, player, cards, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Card(s) played (%(player)s): %(cards)s") % {"player":player, "cards":cards})
         if self.chat_wins.has_key(room_jid):
             self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards)
    
     def tarotInvalidCards(self, room_jid, phase, played_cards, invalid_cards, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug (_("Cards played are not valid: %s") % invalid_cards)
         if self.chat_wins.has_key(room_jid):
@@ -340,7 +340,7 @@
    
     def subscribe(self, type, raw_jid, profile):
         """Called when a subsciption management signal is received"""
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         entity = JID(raw_jid)
         if type=="subscribed":
@@ -364,7 +364,7 @@
         pass  #FIXME
     
     def paramUpdate(self, name, value, namespace, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace':namespace, 'name':name, 'value':value})
         if (namespace,name) == ("Connection", "JabberID"):
@@ -374,7 +374,7 @@
             self.profiles[profile]['watched']=value.split()
 
     def contactDeleted(self, jid, profile):
-        if not self.__check_profile(profile):
+        if not self.check_profile(profile):
             return
         target = JID(jid)
         self.CM.remove(target)
--- a/frontends/wix/contact_list.py	Thu Jul 08 19:47:54 2010 +0800
+++ b/frontends/wix/contact_list.py	Mon Jul 12 17:50:00 2010 +0800
@@ -95,7 +95,6 @@
         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>