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:
parent
bad54b993d
commit
61f6edae54
7 changed files with 213 additions and 26 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
Loading…
Reference in a new issue