# HG changeset patch # User Goffi # Date 1278589674 -28800 # Node ID 961e0898271f3a7b6859dd3e75c7aec0791a03e8 # Parent 34766e0cf970e8cecc8b28470158415a5ba9a765 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 diff -r 34766e0cf970 -r 961e0898271f frontends/primitivus/chat.py --- 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() diff -r 34766e0cf970 -r 961e0898271f frontends/primitivus/contact_list.py --- 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): diff -r 34766e0cf970 -r 961e0898271f frontends/primitivus/custom_widgets.py --- 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) + + diff -r 34766e0cf970 -r 961e0898271f frontends/primitivus/primitivus --- 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()) diff -r 34766e0cf970 -r 961e0898271f frontends/quick_frontend/quick_app.py --- 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 diff -r 34766e0cf970 -r 961e0898271f frontends/quick_frontend/quick_contact_list.py --- 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""" diff -r 34766e0cf970 -r 961e0898271f frontends/wix/contact_list.py --- 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":