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)
This commit is contained in:
Goffi 2010-07-12 17:50:00 +08:00
commit 5f3463b147
6 changed files with 180 additions and 50 deletions

View file

@ -48,7 +48,14 @@ class ChatText(urwid.FlowWidget):
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 @@ class Chat(urwid.WidgetWrap, QuickChat):
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):

View file

@ -21,6 +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 tools.jid import JID
import custom_widgets
@ -29,13 +30,13 @@ 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 = 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 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
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 __contactClicked(self, list_wid):
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, 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)

View file

@ -95,13 +95,31 @@ class SelectableText(urwid.FlowWidget):
"""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 @@ class SelectableText(urwid.FlowWidget):
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']

View file

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

View file

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

View file

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