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
This commit is contained in:
Goffi 2010-07-08 19:47:54 +08:00
commit 61f6edae54
7 changed files with 213 additions and 26 deletions

View file

@ -22,33 +22,169 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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()

View file

@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
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 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
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 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
def disconnect(self, jid):
"""mark a contact disconnected"""
#self.host.debug()
self.remove(jid)
def remove(self, jid):

View file

@ -137,10 +137,15 @@ class SelectableText(urwid.FlowWidget):
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 @@ class GenericList(urwid.WidgetWrap):
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 @@ class GenericList(urwid.WidgetWrap):
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 @@ class List(urwid.FlowWidget):
"""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 @@ class LabelLine(urwid.LineBox):
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)

View file

@ -60,6 +60,9 @@ const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),
('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 @@ class PrimitivusApp(QuickApp):
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())

View file

@ -249,7 +249,7 @@ class QuickApp():
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 @@ class QuickApp():
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

View file

@ -32,6 +32,9 @@ class QuickContactList():
"""
debug(_("Contact List init"))
self.CM = CM
def __contains__(self, jid):
raise NotImplementedError
def clear_contacts(self, jid):
"""Clear all the contact list"""

View file

@ -29,6 +29,9 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
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":