changeset 124:961e0898271f

primitivus chat window - management of one 2 one / group chat - timestamp displayed - added shortcuts for showing/hiding panels - color used - fixed vcard bug (contact displayed even if not from current profile if vcard changed/not in cache) - added VerticalSeparator widget - *List widgets can now use an other widget than SelectableText - new UnselectableText widget
author Goffi <goffi@goffi.org>
date Thu, 08 Jul 2010 19:47:54 +0800
parents 34766e0cf970
children 8d611eb9ae48
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/quick_frontend/quick_contact_list.py frontends/wix/contact_list.py
diffstat 7 files changed, 213 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/primitivus/chat.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/primitivus/chat.py	Thu Jul 08 19:47:54 2010 +0800
@@ -22,33 +22,169 @@
 import urwid
 from quick_frontend.quick_contact_list import QuickContactList
 from quick_frontend.quick_chat import QuickChat
-from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert,SelectableText,LabelLine,SurroundedText
+import custom_widgets
+import time
+from tools.jid  import JID
+
+
+class ChatText(urwid.FlowWidget):
+    """Manage the printing of chat message"""
+    
+    def __init__(self, parent, timestamp, my_jid, from_jid, message, align='left'):
+        self.parent = parent
+        self.timestamp = time.localtime(timestamp)
+        self.my_jid = my_jid
+        self.from_jid = from_jid
+        self.message = unicode(message)
+        self.align = align
+
+    def selectable(self):
+        return True
 
+    def keypress(self, size, key):
+        return key
+
+    def rows(self,size,focus=False):
+        return self.display_widget(size, focus).rows(size, focus)
+
+    def render(self, size, focus=False):
+        return self.display_widget(size, focus).render(size, focus)
+
+    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
+        render_txt = []
+        if self.parent.show_timestamp:
+            time_format = "%c" if self.timestamp < self.parent.day_change else "%H:%M" #if the message was sent before today, we print the full date
+            render_txt.append(('date',"[%s]" % time.strftime(time_format, self.timestamp)))
+        if self.parent.show_short_nick:
+            render_txt.append(('my_nick' if my_mess else 'other_nick',"**" if my_mess else "*"))
+        else:
+            render_txt.append(('my_nick' if my_mess else 'other_nick',"[%s] " % self.from_jid))
+        render_txt.append(self.message)
+        return urwid.Text(render_txt, align=self.align)
 
 class Chat(urwid.WidgetWrap, QuickChat):
 
     def __init__(self, target, host, type='one2one'):
+        self.target = target
         QuickChat.__init__(self, target, host, type)
         self.content = urwid.SimpleListWalker([])
         self.text_list = urwid.ListBox(self.content)
-        main_widget = LabelLine(
-                      urwid.Frame(self.text_list), SurroundedText(str(target))
-                      )
-        urwid.WidgetWrap.__init__(self, main_widget)
+        self.chat_widget = urwid.Frame(self.text_list)
+        self.columns = urwid.Columns([('weight', 8, self.chat_widget)])
+        urwid.WidgetWrap.__init__(self, self.__getDecoration(self.columns))
         self.setType(type)
-    
+        self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00  %Y")) #struct_time of day changing time
+        self.show_timestamp = True
+        self.show_short_nick = False
+        self.show_title = True
+        self.subject = None
+
+    def keypress(self, size, key):
+        if key == "meta p": #user wants to (un)hide the presents panel
+            if self.type == 'group':
+                widgets = self.columns.widget_list
+                if self.present_panel in widgets:
+                    self.__removePresentPanel()
+                else:
+                    self.__appendPresentPanel()
+        elif key == "meta t": #user wants to (un)hide timestamp
+            self.show_timestamp = not self.show_timestamp
+            for wid in self.content:
+                wid._invalidate()
+        elif key == "meta n": #user wants to (not) use short nick
+            self.show_short_nick = not self.show_short_nick
+            for wid in self.content:
+                wid._invalidate()
+        elif key == "meta l": #user wants to (un)hide widget decoration
+            show = not self._w.__class__ == custom_widgets.LabelLine
+            self.showDecoration(show)
+            self._invalidate()
+        elif key == "meta s": #user wants to (un)hide group's subject
+            if self.subject:
+                self.show_title = not self.show_title
+                if self.show_title:
+                    self.setSubject(self.subject)
+                else:
+                    self.chat_widget.header = None
+                self._invalidate()
+
+
+        return super(Chat, self).keypress(size, key) 
+
     def setType(self, type):
         QuickChat.setType(self, type)
         if type == 'one2one':
             self.historyPrint(profile=self.host.profile)
-    
+        elif type == 'group':
+            if len(self.columns.widget_list) == 1:
+                present_widget = self.__buildPresentList()
+                self.present_panel = custom_widgets.VerticalSeparator(present_widget)
+                self.__appendPresentPanel()
+          
+    def __getDecoration(self, widget):
+        return custom_widgets.LabelLine(widget, custom_widgets.SurroundedText(unicode(self.target)))
+
+    def showDecoration(self, show=True):
+        if show:
+            main_widget = self.__getDecoration(self.columns)
+        else:
+            main_widget = self.columns
+        self._w = main_widget
+
+
+    def __buildPresentList(self):
+        self.present_wid = custom_widgets.GenericList([],option_type = custom_widgets.UnselectableText)
+        return self.present_wid
+   
+    def __appendPresentPanel(self):
+        self.columns.widget_list.append(self.present_panel) 
+        self.columns.column_types.append(('weight', 2))
+
+    def __removePresentPanel(self):
+        self.columns.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.columns
+        self.columns.widget_list.remove(self.present_panel)
+        del self.columns.column_types[-1]
+
     def setSubject(self, subject):
         """Set title for a group chat"""
         QuickChat.setSubject(self, subject)
-        self._w.base_widget.header = urwid.AttrMap(urwid.Text(unicode(subject),align='center'),'title')
+        self.subject = subject
+        self.chat_widget.header = urwid.AttrMap(urwid.Text(unicode(subject),align='center'),'title')
+
+    def setPresents(self, param_nicks):
+        """Set the users presents in the contact list for a group chat
+        @param nicks: list of nicknames
+        """
+        nicks = [unicode(nick) for nick in param_nicks] #FIXME: should be done in DBus bridge
+        nicks.sort()
+        QuickChat.setPresents(self, nicks)
+        self.present_wid.changeValues(nicks)
+        self.host.redraw()
 
+    def replaceUser(self, param_nick):
+        """Add user if it is not in the group list"""
+        nick = unicode(param_nick) #FIXME: should be done in DBus bridge
+        if "facebook" in nick:
+            self.host.debug()
+        QuickChat.replaceUser(self, nick)
+        presents = self.present_wid.getAllValues()
+        if nick not in presents:
+            presents.append(nick)
+            presents.sort()
+            self.present_wid.changeValues(presents)
+        self.host.redraw()
+
+    def removeUser(self, param_nick):
+        """Remove a user from the group list"""
+        nick = unicode(param_nick) #FIXME: should be done in DBus bridge
+        QuickChat.removeUser(self, nick)
+        self.present_wid.deleteValue(nick)
+        self.host.redraw()
 
     def printMessage(self, from_jid, msg, profile, timestamp=""):
-        self.content.append(SelectableText("[%s] " % from_jid + msg))
+        assert (from_jid.__class__ == JID)
+        my_jid = self.host.profiles[profile]['whoami']
+        self.content.append(ChatText(self, timestamp or None, my_jid, from_jid, msg))
         self.text_list.set_focus(len(self.content)-1)
         self.host.redraw()
--- a/frontends/primitivus/contact_list.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/primitivus/contact_list.py	Thu Jul 08 19:47:54 2010 +0800
@@ -21,7 +21,7 @@
 
 import urwid
 from quick_frontend.quick_contact_list import QuickContactList
-from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert,LabelLine,SurroundedText
+import custom_widgets
 
 
 class ContactList(urwid.WidgetWrap, QuickContactList):
@@ -30,13 +30,12 @@
     def __init__(self, host, CM, on_click=None, on_change=None, user_data=None):
         self.host = host
                
-        self.list_wid = List([], style=['single','no_first_select'], align='left', on_click=self.__contactClicked, on_change=on_change)
+        self.list_wid = custom_widgets.GenericList([], style=['single','no_first_select'], align='left', on_click=self.__contactClicked, on_change=on_change)
 
         #we now build the widget
-        body_content = urwid.SimpleListWalker([self.list_wid])
-        frame_body = urwid.ListBox(body_content)
+        frame_body = self.list_wid
         frame = urwid.Frame(frame_body)
-        self.main_widget = LabelLine(frame,SurroundedText(_("Contacts")))
+        self.main_widget = custom_widgets.LabelLine(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,6 +43,10 @@
             urwid.connect_signal(self, 'change', on_change, user_data)
         QuickContactList.__init__(self, CM)
 
+    def __contains__(self, jid):
+        contacts = self.list_wid.getAllValues()
+        return jid.short in contacts
+
     def __contactClicked(self, list_wid):
         self._emit('click')
 
@@ -66,7 +69,6 @@
     
     def disconnect(self, jid):
         """mark a contact disconnected"""
-        #self.host.debug()
         self.remove(jid)
     
     def remove(self, jid):
--- a/frontends/primitivus/custom_widgets.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/primitivus/custom_widgets.py	Thu Jul 08 19:47:54 2010 +0800
@@ -137,10 +137,15 @@
             attr+="_focus"
         return urwid.Text((attr,self.text), align=self.align)
 
+class UnselectableText(SelectableText):
+    
+    def setState(self, selected, invisible=False):
+        pass
+
 class GenericList(urwid.WidgetWrap):
     signals = ['click','change']
 
-    def __init__(self, options, style=[], align='left', on_click=None, on_change=None, user_data=None):
+    def __init__(self, options, style=[], align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None):
         """
         Widget managing list of string and their selection
         @param options: list of strings used for options
@@ -156,6 +161,7 @@
         self.no_first_select = 'no_first_select' in style
         self.can_select_none = 'can_select_none' in style
         self.align = align
+        self.option_type = option_type
         self.first_display = True
         
         if on_click:
@@ -222,11 +228,14 @@
             old_selected = self.getSelectedValues()
         widgets = []
         for option in new_values:
-            widget = SelectableText(option, self.align)
+            widget = self.option_type(option, self.align)
             if not self.first_display and option in old_selected:
                 widget.setState(True)
             widgets.append(widget)
-            urwid.connect_signal(widget, 'change', self.__onStateChange)
+            try:
+                urwid.connect_signal(widget, 'change', self.__onStateChange)
+            except NameError:
+                pass #the widget given doesn't support 'change' signal
         self.content[:] = widgets
         if self.first_display and self.single and new_values and not self.no_first_select:
             self.content[0].setState(True)
@@ -249,8 +258,8 @@
     """FlowWidget list, same arguments as GenericList, with an additional one 'max_height'"""
     signals = ['click','change']
 
-    def __init__(self, options, style=[], max_height=5, align='left', on_click=None, on_change=None, user_data=None):
-        self.genericList = GenericList(options, style, align, on_click, on_change, user_data)
+    def __init__(self, options, style=[], max_height=5, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None):
+        self.genericList = GenericList(options, style, align, option_type, on_click, on_change, user_data)
         self.max_height = max_height 
 
     def selectable(self):
@@ -365,4 +374,18 @@
         urwid.LineBox.__init__(self, original_widget)
         top_columns = self._w.widget_list[0]
         top_columns.widget_list[1] = label_widget
+
+class VerticalSeparator(urwid.WidgetDecoration, urwid.WidgetWrap):
+    def __init__(self, original_widget, left_char = utf8decode("│"), right_char = ''):
+        """Draw a separator on left and/or of original_widget."""
         
+        widgets = [original_widget]
+        if left_char:
+            widgets.insert(0, ('fixed', 1, urwid.SolidFill(left_char)))
+        if right_char:
+            widgets.append(('fixed', 1, urwid.SolidFill(right_char)))
+        columns = urwid.Columns(widgets, box_columns = [0,2], focus_column = 1)
+        urwid.WidgetDecoration.__init__(self, original_widget)
+        urwid.WidgetWrap.__init__(self, columns)
+
+        
--- a/frontends/primitivus/primitivus	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/primitivus/primitivus	Thu Jul 08 19:47:54 2010 +0800
@@ -60,6 +60,9 @@
                  ('selected_focus', 'default,bold', 'dark red'),
                  ('default', 'default', 'default'),
                  ('default_focus', 'default,bold', 'default'),
+                 ('date', 'light gray', 'default'),
+                 ('my_nick', 'dark red,bold', 'default'),
+                 ('other_nick', 'dark cyan,bold', 'default'),
                  ]
             
 class ChatList(QuickChatList):
@@ -108,6 +111,21 @@
         elif input == 'meta j': #user wants to join a room
             pop_up_widget = InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'test@conference.necton2.int', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom)
             self.showPopUp(pop_up_widget)
+        elif input == 'f2': #user wants to (un)hide the contact_list
+            try:
+                center_widgets = self.center_part.widget_list
+                if self.contactList in center_widgets:
+                    self.center_part.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.center_part
+                    center_widgets.remove(self.contactList)
+                    del self.center_part.column_types[0]
+                else:
+                    center_widgets.insert(0, self.contactList)
+                    self.center_part.column_types.insert(0, ('weight', 2))
+            except AttributeError:
+                #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())
--- a/frontends/quick_frontend/quick_app.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/quick_frontend/quick_app.py	Thu Jul 08 19:47:54 2010 +0800
@@ -249,7 +249,7 @@
         self.chat_wins[room_jid].setUserNick(user_nick)
         self.chat_wins[room_jid].setType("group")
         self.chat_wins[room_jid].id = room_jid
-        self.chat_wins[room_jid].setPresents([user_nick]+room_nicks)
+        self.chat_wins[room_jid].setPresents(list(set([user_nick]+room_nicks)))
 
 
     def roomUserJoined(self, room_id, room_service, user_nick, user_data, profile):
@@ -387,13 +387,15 @@
     def updatedValue(self, name, data):
         if name == "card_nick":
             target = JID(data['jid'])
-            self.CM.update(target, 'nick', data['nick'])
-            self.contactList.replace(target)
+            if target in self.contactList:
+                self.CM.update(target, 'nick', data['nick'])
+                self.contactList.replace(target)
         elif name == "card_avatar":
             target = JID(data['jid'])
-            filename = self.bridge.getAvatarFile(data['avatar'])
-            self.CM.update(target, 'avatar', filename)
-            self.contactList.replace(target)
+            if target in self.contactList:
+                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_contact_list.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/quick_frontend/quick_contact_list.py	Thu Jul 08 19:47:54 2010 +0800
@@ -32,6 +32,9 @@
         """
         debug(_("Contact List init"))
         self.CM = CM
+
+    def __contains__(self, jid):
+        raise NotImplementedError
     
     def clear_contacts(self, jid):
         """Clear all the contact list"""
--- a/frontends/wix/contact_list.py	Thu Jul 08 18:29:44 2010 +0800
+++ b/frontends/wix/contact_list.py	Thu Jul 08 19:47:54 2010 +0800
@@ -29,6 +29,9 @@
         self.groups = {}  #list contacts in each groups, key = group
         self.Bind(wx.EVT_LISTBOX, self.onSelected)
         self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated)
+    
+    def __contains__(self, jid):
+        return bool(self.__find_idx(jid))
 
     def __typeSwitch(self):
         if self.type == "JID":