core, quick_frontend, primitivus, wix: Contacts List refactoring phase 1:

- QuickContactManagement is not used anymore and will be removed, ContactList + Core are used instead
	- disconnected contacts are now displayed in Primitivus (M-d to show/hide them)
	- avatars are temporary unavailable in wix
	- new bridge method: getContactsFromGroup
This commit is contained in:
Goffi 2012-09-25 00:58:34 +02:00
commit b44412ec0d
17 changed files with 207 additions and 130 deletions

View file

@ -96,6 +96,9 @@ class DBusBridgeFrontend(BridgeFrontend):
def getContacts(self, profile_key="@DEFAULT@"): def getContacts(self, profile_key="@DEFAULT@"):
return self.db_core_iface.getContacts(profile_key) return self.db_core_iface.getContacts(profile_key)
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self.db_core_iface.getContactsFromGroup(group, profile_key)
def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None): def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:])) return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:]))

View file

@ -262,11 +262,8 @@ class Chat(urwid.WidgetWrap, QuickChat):
def startGame(self, game_type, referee, players): def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game""" """Configure the chat window to start a game"""
if game_type=="Tarot": if game_type=="Tarot":
try: self.tarot_wid = CardGame(self, referee, players, self.nick)
self.tarot_wid = CardGame(self, referee, players, self.nick) self.__appendGamePanel(self.tarot_wid)
self.__appendGamePanel(self.tarot_wid)
except e:
self.host.debug()
def getGame(self, game_type): def getGame(self, game_type):
"""Return class managing the game type""" """Return class managing the game type"""
@ -288,6 +285,11 @@ class Chat(urwid.WidgetWrap, QuickChat):
#MISC EVENTS# #MISC EVENTS#
def onFileSelected(self, filepath): def onFileSelected(self, filepath):
self.host.removePopUp() self.host.removePopUp()
full_jid = self.host.CM.get_full(self.target) #FIXME: check last_resource: what if self.target.resource exists ?
last_resource = self.host.bridge.getLastResource(unicode(self.target.short), self.host.profile)
if last_resource:
full_jid = JID("%s/%s" % (self.target.short, last_resource))
else:
full_jid = self.target
id = self.host.bridge.sendFile(full_jid, filepath, {}, self.host.profile) id = self.host.bridge.sendFile(full_jid, filepath, {}, self.host.profile)
self.host.addProgress(id,filepath) self.host.addProgress(id,filepath)

View file

@ -54,6 +54,7 @@ __builtin__.__dict__['const_PALETTE'] = [
('warning', 'light red', 'default'), ('warning', 'light red', 'default'),
('progress_normal', 'default', 'black'), ('progress_normal', 'default', 'black'),
('progress_complete', 'default', 'light red'), ('progress_complete', 'default', 'light red'),
('show_disconnected', 'dark gray', 'default'),
('show_normal', 'default', 'default'), ('show_normal', 'default', 'default'),
('show_normal_focus', 'default, bold', 'default'), ('show_normal_focus', 'default, bold', 'default'),
('show_chat', 'dark green', 'default'), ('show_chat', 'dark green', 'default'),
@ -67,7 +68,8 @@ __builtin__.__dict__['const_PALETTE'] = [
('status', 'yellow', 'default'), ('status', 'yellow', 'default'),
('status_focus', 'yellow, bold', 'default'), ('status_focus', 'yellow, bold', 'default'),
] ]
__builtin__.__dict__['const_SHOW_ICON'] = {"": (u'✔', "show_normal"), __builtin__.__dict__['const_SHOW_ICON'] = {"unavailable": (u'⨯', "show_disconnected"),
"": (u'✔', "show_normal"),
"chat": (u'✆', "show_chat"), "chat": (u'✆', "show_chat"),
"away": (u'✈', "show_away"), "away": (u'✈', "show_away"),
"dnd": (u'✖', "show_dnd"), "dnd": (u'✖', "show_dnd"),

View file

@ -28,12 +28,13 @@ from sat.tools.jid import JID
class ContactList(urwid.WidgetWrap, QuickContactList): class ContactList(urwid.WidgetWrap, QuickContactList):
signals = ['click','change'] signals = ['click','change']
def __init__(self, host, CM, on_click=None, on_change=None, user_data=None): def __init__(self, host, on_click=None, on_change=None, user_data=None):
self.host = host self.host = host
self.selected = None self.selected = None
self.groups={} self.groups={}
self.alert_jid=set() self.alert_jid=set()
self.show_status = False self.show_status = False
self.show_disconnected = False
#we now build the widget #we now build the widget
self.frame = urwid.Frame(self.__buildList()) self.frame = urwid.Frame(self.__buildList())
@ -43,9 +44,9 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
urwid.connect_signal(self, 'click', on_click, user_data) urwid.connect_signal(self, 'click', on_click, user_data)
if on_change: if on_change:
urwid.connect_signal(self, 'change', on_change, user_data) urwid.connect_signal(self, 'change', on_change, user_data)
QuickContactList.__init__(self, CM) QuickContactList.__init__(self)
def _update(self): def update(self):
"""Update display, keep focus""" """Update display, keep focus"""
widget, position = self.frame.body.get_focus() widget, position = self.frame.body.get_focus()
self.frame.body = self.__buildList() self.frame.body = self.__buildList()
@ -53,10 +54,16 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
self.frame.body.set_focus(position) self.frame.body.set_focus(position)
self.host.redraw() self.host.redraw()
def update_jid(self, jid):
self.update()
def keypress(self, size, key): def keypress(self, size, key):
if key == "meta s": #user wants to (un)hide contacts' statuses if key == "meta s": #user wants to (un)hide contacts' statuses
self.show_status = not self.show_status self.show_status = not self.show_status
self._update() self.update()
elif key == "meta d": #user wants to (un)hide disconnected contacts
self.show_disconnected = not self.show_disconnected
self.update()
return super(ContactList, self).keypress(size, key) return super(ContactList, self).keypress(size, key)
def __contains__(self, jid): def __contains__(self, jid):
@ -77,12 +84,12 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
def putAlert(self, jid): def putAlert(self, jid):
"""Put an alert on the jid to get attention from user (e.g. for new message)""" """Put an alert on the jid to get attention from user (e.g. for new message)"""
self.alert_jid.add(jid.short) self.alert_jid.add(jid.short)
self._update() self.update()
def __groupClicked(self, group_wid): def __groupClicked(self, group_wid):
group = self.groups[group_wid.getValue()] group = self.groups[group_wid.getValue()]
group[0] = not group[0] group[0] = not group[0]
self._update() self.update()
self.setFocus(group_wid.getValue()) self.setFocus(group_wid.getValue())
def __contactClicked(self, contact_wid, selected): def __contactClicked(self, contact_wid, selected):
@ -92,7 +99,7 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
widget.setState(widget.data == self.selected, invisible=True) widget.setState(widget.data == self.selected, invisible=True)
if self.selected in self.alert_jid: if self.selected in self.alert_jid:
self.alert_jid.remove(self.selected) self.alert_jid.remove(self.selected)
self._update() self.update()
self._emit('click') self._emit('click')
def __buildContact(self, content, param_contacts): def __buildContact(self, content, param_contacts):
@ -100,13 +107,20 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
@param content: widget list, e.g. SimpleListWalker @param content: widget list, e.g. SimpleListWalker
@param contacts: list of JID""" @param contacts: list of JID"""
contacts = list(param_contacts) contacts = list(param_contacts)
contacts.sort()
widgets = [] #list of built widgets
for contact in contacts: for contact in contacts:
jid=JID(contact) jid=JID(contact)
name = self.CM.getAttr(jid,'name') name = self.getCache(jid, 'name')
nick = self.CM.getAttr(jid,'nick') nick = self.getCache(jid, 'nick')
status = self.CM.getAttr(jid, 'status') status = self.getCache(jid, 'status')
show = self.CM.getAttr(jid, 'show') show = self.getCache(jid, 'show')
if show == None:
show = "unavailable"
if (not self.show_disconnected and show == "unavailable"
and not contact in self.alert_jid and contact != self.selected):
continue
show_icon, show_attr = const_SHOW_ICON.get(show,('','default')) show_icon, show_attr = const_SHOW_ICON.get(show,('','default'))
contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.short) contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.short)
display = [ show_icon + " " , contact_disp] display = [ show_icon + " " , contact_disp]
@ -118,6 +132,12 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
selected = contact==self.selected, selected = contact==self.selected,
header=header) header=header)
widget.data = contact widget.data = contact
widget.comp = contact_disp[1].lower() #value to use for sorting
widgets.append(widget)
widgets.sort(key=lambda widget: widget.comp)
for widget in widgets:
content.append(widget) content.append(widget)
urwid.connect_signal(widget, 'change', self.__contactClicked) urwid.connect_signal(widget, 'change', self.__contactClicked)
@ -125,7 +145,7 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
"""Build the main contact list widget""" """Build the main contact list widget"""
content = urwid.SimpleListWalker([]) content = urwid.SimpleListWalker([])
group_keys = self.groups.keys() group_keys = self.groups.keys()
group_keys.sort() group_keys.sort(key = lambda x: x.lower() if x else x)
for key in group_keys: for key in group_keys:
unfolded = self.groups[key][0] unfolded = self.groups[key][0]
if key!=None: if key!=None:
@ -145,28 +165,30 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
widget.setState(False, invisible=True) widget.setState(False, invisible=True)
def get_contact(self): def getContact(self):
"""Return contact currently selected""" """Return contact currently selected"""
return self.selected return self.selected
def clear_contacts(self): def clearContacts(self):
"""clear all the contact list""" """clear all the contact list"""
self.groups={} self.groups={}
self.selected = None self.selected = None
self.unselectAll() self.unselectAll()
self._update() self.update()
def replace(self, jid, groups=[None]): def replace(self, jid, groups=None, attributes=None):
"""add a contact to the list if doesn't exist, else update it""" """add a contact to the list if doesn't exist, else update it"""
if not groups:
groups = [None]
if not attributes:
attributes={}
assert isinstance(groups, list) assert isinstance(groups, list)
assert isinstance(jid, JID) assert isinstance(jid, JID)
if not groups:
groups=[None]
for group in groups: for group in groups:
if not self.groups.has_key(group): if not self.groups.has_key(group):
self.groups[group] = [True,set()] #[unfold,list_of_contacts] self.groups[group] = [True,set()] #[unfold,list_of_contacts]
self.groups[group][1].add(jid.short) self.groups[group][1].add(jid.short)
self._update() self.update()
"""contacts = self.list_wid.getAllValues() """contacts = self.list_wid.getAllValues()
@ -176,10 +198,6 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
self.list_wid.changeValues(contacts) self.list_wid.changeValues(contacts)
self._emit('change')""" self._emit('change')"""
def disconnect(self, jid):
"""mark a contact disconnected"""
self.remove(jid.short)
def remove(self, param_jid): def remove(self, param_jid):
"""remove a contact from the list""" """remove a contact from the list"""
groups_to_remove = [] groups_to_remove = []
@ -192,7 +210,7 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
groups_to_remove.append(group) groups_to_remove.append(group)
for group in groups_to_remove: for group in groups_to_remove:
del self.groups[group] del self.groups[group]
self._update() self.update()
def add(self, jid, param_groups=[None]): def add(self, jid, param_groups=[None]):
"""add a contact to the list""" """add a contact to the list"""

View file

@ -56,7 +56,6 @@ class ChatList(QuickChatList):
class PrimitivusApp(QuickApp): class PrimitivusApp(QuickApp):
def __init__(self): def __init__(self):
self.CM = QuickContactManagement() #FIXME: not the best place
QuickApp.__init__(self) QuickApp.__init__(self)
## main loop setup ## ## main loop setup ##
@ -141,12 +140,12 @@ class PrimitivusApp(QuickApp):
elif input == 'f2': #user wants to (un)hide the contact_list elif input == 'f2': #user wants to (un)hide the contact_list
try: try:
center_widgets = self.center_part.widget_list center_widgets = self.center_part.widget_list
if self.contactList in center_widgets: if self.contact_list 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 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) center_widgets.remove(self.contact_list)
del self.center_part.column_types[0] del self.center_part.column_types[0]
else: else:
center_widgets.insert(0, self.contactList) center_widgets.insert(0, self.contact_list)
self.center_part.column_types.insert(0, ('weight', 2)) self.center_part.column_types.insert(0, ('weight', 2))
except AttributeError: except AttributeError:
#The main widget is not built (probably in Profile Manager) #The main widget is not built (probably in Profile Manager)
@ -196,9 +195,9 @@ class PrimitivusApp(QuickApp):
return menu_roller return menu_roller
def __buildMainWidget(self): def __buildMainWidget(self):
self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw()) self.contact_list = ContactList(self, on_click = self.contactSelected, on_change=lambda w: self.redraw())
#self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))]) #self.center_part = urwid.Columns([('weight',2,self.contact_list),('weight',8,Chat('',self))])
self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))]) self.center_part = urwid.Columns([('weight',2,self.contact_list), ('weight',8,urwid.Filler(urwid.Text('')))])
self.editBar = sat_widgets.AdvancedEdit(u'> ') self.editBar = sat_widgets.AdvancedEdit(u'> ')
self.editBar.setCompletionMethod(self._nick_completion) self.editBar.setCompletionMethod(self._nick_completion)
urwid.connect_signal(self.editBar,'click',self.onTextEntered) urwid.connect_signal(self.editBar,'click',self.onTextEntered)
@ -209,7 +208,7 @@ class PrimitivusApp(QuickApp):
def _nick_completion(self, text, completion_data): def _nick_completion(self, text, completion_data):
"""Completion method which complete pseudo in group chat """Completion method which complete pseudo in group chat
for params, see AdvancedEdit""" for params, see AdvancedEdit"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectable at once contact = self.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once
if contact: if contact:
chat = self.chat_wins[contact] chat = self.chat_wins[contact]
if chat.type != "group": if chat.type != "group":
@ -266,7 +265,7 @@ class PrimitivusApp(QuickApp):
wid_idx = len(self.center_part.widget_list)-1 wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = widget self.center_part.widget_list[wid_idx] = widget
self.menu_roller.removeMenu(_('Chat menu')) self.menu_roller.removeMenu(_('Chat menu'))
self.contactList.unselectAll() self.contact_list.unselectAll()
self.redraw() self.redraw()
def removeWindow(self): def removeWindow(self):
@ -289,7 +288,7 @@ class PrimitivusApp(QuickApp):
self.notBar.setProgress(percentage) self.notBar.setProgress(percentage)
def contactSelected(self, contact_list): def contactSelected(self, contact_list):
contact = contact_list.get_contact() contact = contact_list.getContact()
if contact: if contact:
assert(len(self.center_part.widget_list)==2) assert(len(self.center_part.widget_list)==2)
self.center_part.widget_list[1] = self.chat_wins[contact] self.center_part.widget_list[1] = self.chat_wins[contact]
@ -297,7 +296,7 @@ class PrimitivusApp(QuickApp):
def onTextEntered(self, editBar): def onTextEntered(self, editBar):
"""Called when text is entered in the main edit bar""" """Called when text is entered in the main edit bar"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once contact = self.contact_list.getContact() ###Based on the fact that there is currently only one contact selectableat once
if contact: if contact:
chat = self.chat_wins[contact] chat = self.chat_wins[contact]
self.bridge.sendMessage(contact, self.bridge.sendMessage(contact,
@ -311,8 +310,8 @@ class PrimitivusApp(QuickApp):
return return
QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile) QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
sender = JID(from_jid) sender = JID(from_jid)
if JID(self.contactList.selected).short != sender.short: if JID(self.contact_list.selected).short != sender.short:
self.contactList.putAlert(sender) self.contact_list.putAlert(sender)
def _dialogOkCb(self, widget, data): def _dialogOkCb(self, widget, data):
self.removePopUp() self.removePopUp()
@ -422,8 +421,8 @@ class PrimitivusApp(QuickApp):
def onRemoveContact(self, button): def onRemoveContact(self, button):
self.removePopUp() self.removePopUp()
info(_("Unsubscribing %s presence"),self.contactList.get_contact()) info(_("Unsubscribing %s presence"),self.contact_list.getContact())
self.bridge.delContact(self.contactList.get_contact(), profile_key=self.profile) self.bridge.delContact(self.contact_list.getContact(), profile_key=self.profile)
#MENU EVENTS# #MENU EVENTS#
def onConnectRequest(self, menu): def onConnectRequest(self, menu):
@ -460,7 +459,7 @@ class PrimitivusApp(QuickApp):
self.showPopUp(pop_up_widget) self.showPopUp(pop_up_widget)
def onRemoveContactRequest(self, menu): def onRemoveContactRequest(self, menu):
contact = self.contactList.get_contact() contact = self.contact_list.getContact()
if not contact: if not contact:
self.showPopUp(sat_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp)) self.showPopUp(sat_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp))
else: else:

View file

@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from logging import debug, info, error from logging import debug, info, warning, error
from sat.tools.jid import JID from sat.tools.jid import JID
from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
from optparse import OptionParser from optparse import OptionParser
@ -32,7 +32,6 @@ class QuickApp():
"""This class contain the main methods needed for the frontend""" """This class contain the main methods needed for the frontend"""
def __init__(self, single_profile=True): def __init__(self, single_profile=True):
self.rosterList = {}
self.profiles = {} self.profiles = {}
self.single_profile = single_profile self.single_profile = single_profile
self.check_options() self.check_options()
@ -157,7 +156,7 @@ class QuickApp():
### now we fill the contact list ### ### now we fill the contact list ###
for contact in self.bridge.getContacts(profile): for contact in self.bridge.getContacts(profile):
self.newContact(contact[0], contact[1], contact[2], profile) self.newContact(*contact, profile=profile)
presences = self.bridge.getPresenceStatus(profile) presences = self.bridge.getPresenceStatus(profile)
for contact in presences: for contact in presences:
@ -207,8 +206,7 @@ class QuickApp():
if not self.check_profile(profile): if not self.check_profile(profile):
return return
debug(_("Disconnected")) debug(_("Disconnected"))
self.CM.clear() self.contactList.clearContacts()
self.contactList.clear_contacts()
self.setStatusOnline(False) self.setStatusOnline(False)
def connectionError(self, error_type, profile): def connectionError(self, error_type, profile):
@ -227,10 +225,7 @@ class QuickApp():
return return
entity=JID(JabberId) entity=JID(JabberId)
_groups = list(groups) _groups = list(groups)
self.rosterList[entity.short]=(dict(attributes), _groups) self.contact_list._replace(entity, _groups, attributes)
if entity in self.CM:
self.CM.update(entity, 'groups', _groups)
self.contactList.replace(entity, self.CM.getAttr(entity, 'groups'))
def newMessage(self, from_jid, msg, type, to_jid, profile): def newMessage(self, from_jid, msg, type, to_jid, profile):
if not self.check_profile(profile): if not self.check_profile(profile):
@ -266,36 +261,20 @@ class QuickApp():
self.setStatusOnline(False) self.setStatusOnline(False)
return return
self.contact_list.updatePresence(from_jid, show, priority, statuses)
if show != 'unavailable': if show != 'unavailable':
name=""
groups = []
if self.rosterList.has_key(from_jid.short):
if self.rosterList[from_jid.short][0].has_key("name"):
name=self.rosterList[from_jid.short][0]["name"]
groups=self.rosterList[from_jid.short][1]
#FIXME: must be moved in a plugin #FIXME: must be moved in a plugin
if from_jid.short in self.profiles[profile]['watched'] and not from_jid.short in self.profiles[profile]['onlineContact']: if from_jid.short in self.profiles[profile]['watched'] and not from_jid.short in self.profiles[profile]['onlineContact']:
self.showAlert(_("Watched jid [%s] is connected !") % from_jid.short) self.showAlert(_("Watched jid [%s] is connected !") % from_jid.short)
self.profiles[profile]['onlineContact'].add(from_jid) #FIXME onlineContact is useless with CM, must be removed self.profiles[profile]['onlineContact'].add(from_jid) #FIXME onlineContact is useless with CM, must be removed
self.CM.add(from_jid)
self.CM.update(from_jid, 'name', unicode(name)) #TODO: vcard data (avatar)
self.CM.update(from_jid, 'show', show)
self.CM.update(from_jid, 'statuses', statuses)
self.CM.update(from_jid, 'groups', groups)
cache = self.bridge.getCardCache(from_jid, profile)
if cache.has_key('nick'):
self.CM.update(from_jid, 'nick', unicode(cache['nick']))
if cache.has_key('avatar'):
self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar']))
self.contactList.replace(from_jid, self.CM.getAttr(from_jid, 'groups'))
if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']: if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']:
self.profiles[profile]['onlineContact'].remove(from_jid) self.profiles[profile]['onlineContact'].remove(from_jid)
self.CM.remove(from_jid)
if not self.CM.isConnected(from_jid):
self.contactList.disconnect(from_jid)
def roomJoined(self, room_jid, room_nicks, user_nick, profile): def roomJoined(self, room_jid, room_nicks, user_nick, profile):
"""Called when a MUC room is joined""" """Called when a MUC room is joined"""
@ -491,27 +470,27 @@ class QuickApp():
if not self.check_profile(profile): if not self.check_profile(profile):
return return
target = JID(jid) target = JID(jid)
self.contactList.remove(self.CM.get_full(target)) self.contactList.remove(target)
self.CM.remove(target)
try: try:
self.profiles[profile]['onlineContact'].remove(target.short) self.profiles[profile]['onlineContact'].remove(target.short)
except KeyError: except KeyError:
pass pass
def updatedValue(self, name, data, profile): def updatedValue(self, name, data, profile):
#FIXME: to be removed
if not self.check_profile(profile): if not self.check_profile(profile):
return return
if name == "card_nick": if name == "card_nick":
target = JID(data['jid']) target = JID(data['jid'])
if target in self.contactList: if target in self.contactList:
self.CM.update(target, 'nick', unicode(data['nick'])) #self.CM.update(target, 'nick', unicode(data['nick']))
self.contactList.replace(target) self.contact_list._replace(target)
elif name == "card_avatar": elif name == "card_avatar":
target = JID(data['jid']) target = JID(data['jid'])
if target in self.contactList: if target in self.contactList:
filename = self.bridge.getAvatarFile(data['avatar']) filename = self.bridge.getAvatarFile(data['avatar'])
self.CM.update(target, 'avatar', filename) #self.CM.update(target, 'avatar', filename)
self.contactList.replace(target) self.contact_list._replace(target)
def askConfirmation(self, type, id, data): def askConfirmation(self, type, id, data):
raise NotImplementedError raise NotImplementedError

View file

@ -99,7 +99,7 @@ class QuickChat():
def _get_nick(self, jid): def _get_nick(self, jid):
"""Return nick of this jid when possible""" """Return nick of this jid when possible"""
return jid.resource if self.type == "group" else (self.host.CM.getAttr(jid,'nick') or self.host.CM.getAttr(jid,'name') or jid.node) return jid.resource if self.type == "group" else (self.host.contact_list.getCache(jid,'nick') or self.host.contact_list.getCache(jid,'name') or jid.node)
def printMessage(self, from_jid, msg, profile, timestamp): def printMessage(self, from_jid, msg, profile, timestamp):
"""Print message in chat window. Must be implemented by child class""" """Print message in chat window. Must be implemented by child class"""

View file

@ -19,33 +19,47 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from logging import debug, info, error from logging import debug
from sat.tools.jid import JID
class QuickContactList(): class QuickContactList():
"""This class manage the visual representation of contacts""" """This class manage the visual representation of contacts"""
def __init__(self, CM): def __init__(self):
"""
@param CM: instance of QuickContactManagement
"""
debug(_("Contact List init")) debug(_("Contact List init"))
self.CM = CM self._cache = {}
def update_jid(self, jid):
"""Update the jid in the list when something changed"""
raise NotImplementedError
def getCache(self, jid, name):
try:
jid_cache = self._cache[jid.short]
if name == 'status': #XXX: we get the first status for 'status' key
return jid_cache['statuses'].get('default','')
return jid_cache[name]
except (KeyError, IndexError):
return None
def setCache(self, jid, name, value):
jid_cache = self._cache.setdefault(jid.short, {})
jid_cache[name] = value
def __contains__(self, jid): def __contains__(self, jid):
raise NotImplementedError raise NotImplementedError
def clear_contacts(self, jid): def clearContacts(self, jid):
"""Clear all the contact list""" """Clear all the contact list"""
raise NotImplementedError raise NotImplementedError
def replace(self, jid, groups=None): def _replace(self, jid, groups=None, attributes=None):
"""add a contact to the list if doesn't exist, else update it""" if 'name' in attributes:
raise NotImplementedError self.setCache(jid, 'name', attributes['name'])
self.replace(jid, groups, attributes)
def disconnect(self, jid): def replace(self, jid, groups, attributes):
"""mark a contact disconnected""" """add a contact to the list if doesn't exist, else update it"""
raise NotImplementedError raise NotImplementedError
def remove(self, jid): def remove(self, jid):
@ -55,3 +69,14 @@ class QuickContactList():
def add(self, jid, param_groups=None): def add(self, jid, param_groups=None):
"""add a contact to the list""" """add a contact to the list"""
raise NotImplementedError raise NotImplementedError
def updatePresence(self, jid, show, priority, statuses):
"""Update entity's presence status
@param jid: entity to update's jid
@param show: availability
@parap priority: resource's priority
@param statuses: dict of statuses"""
self.setCache(jid, 'show', show)
self.setCache(jid, 'prority', priority)
self.setCache(jid, 'statuses', statuses)
self.update_jid(jid)

View file

@ -258,7 +258,12 @@ class Chat(wx.Frame, QuickChat):
filename = wx.FileSelector(_("Choose a file to send"), flags = wx.FD_FILE_MUST_EXIST) filename = wx.FileSelector(_("Choose a file to send"), flags = wx.FD_FILE_MUST_EXIST)
if filename: if filename:
debug(_("filename: %s"),filename) debug(_("filename: %s"),filename)
full_jid = self.host.CM.get_full(self.target) #FIXME: check last_resource: what if self.target.resource exists ?
last_resource = self.host.bridge.getLastResource(unicode(self.target.short), self.host.profile)
if last_resource:
full_jid = JID("%s/%s" % (self.target.short, last_resource))
else:
full_jid = self.target
id = self.host.bridge.sendFile(full_jid, filename, {}, self.host.profile) id = self.host.bridge.sendFile(full_jid, filename, {}, self.host.profile)
self.host.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename)) self.host.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename))

View file

@ -44,7 +44,7 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
"CUSTOM" for a customized contact list (self.__presentItem must then be overrided) "CUSTOM" for a customized contact list (self.__presentItem must then be overrided)
""" """
wx.SimpleHtmlListBox.__init__(self, parent, -1) wx.SimpleHtmlListBox.__init__(self, parent, -1)
QuickContactList.__init__(self, host.CM) QuickContactList.__init__(self)
self.host = host self.host = host
self.type = type self.type = type
self.__typeSwitch() self.__typeSwitch()
@ -72,16 +72,18 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
result.append(i) result.append(i)
return result return result
def replace(self, contact, groups=None): def update_jid(self, jid):
self.replace(jid)
def replace(self, contact, groups=None, attributes=None):
debug(_("update %s") % contact) debug(_("update %s") % contact)
if not self.__find_idx(contact): if not self.__find_idx(contact):
self.add(contact, groups) self.add(contact, groups)
else: else:
for i in self.__find_idx(contact): for i in self.__find_idx(contact):
self.SetString(i, self.__presentItem(contact)) _present = self.__presentItem(contact)
if _present != None:
def disconnect(self, contact): self.SetString(i, _present)
self.remove(contact) #for now, we only show online contacts
def __eraseGroup(self, group): def __eraseGroup(self, group):
"""Erase all contacts in group """Erase all contacts in group
@ -108,15 +110,19 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
def __presentItemJID(self, jid): def __presentItemJID(self, jid):
"""Make a nice presentation of the contact in the list for JID contacts.""" """Make a nice presentation of the contact in the list for JID contacts."""
name = self.CM.getAttr(jid,'name') name = self.getCache(jid,'name')
nick = self.CM.getAttr(jid,'nick') nick = self.getCache(jid,'nick')
show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0] _show = self.getCache(jid,'show')
if _show == None or _show == 'unavailable':
return None
show = filter(lambda x : x[0] == _show, const_STATUS)[0]
#show[0]==shortcut #show[0]==shortcut
#show[1]==human readable #show[1]==human readable
#show[2]==color (or None) #show[2]==color (or None)
show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else "" show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
status = self.CM.getAttr(jid,'status') or '' status = self.getCache(jid,'status') or ''
avatar = self.CM.getAttr(jid,'avatar') or self.empty_avatar #XXX: there is a weird bug here: if the image has an extension (i.e. empty_avatar.png), avatar = self.getCache(jid,'avatar') or self.empty_avatar #XXX: there is a weird bug here: if the image has an extension (i.e. empty_avatar.png),
#WxPython segfault, and it doesn't without nothing. I couldn't reproduce the case with a basic test script, so it need further investigation before reporting it #WxPython segfault, and it doesn't without nothing. I couldn't reproduce the case with a basic test script, so it need further investigation before reporting it
#to WxPython dev. Anyway, the program crash with a segfault, not a python exception, so there is definitely something wrong with WxPython. #to WxPython dev. Anyway, the program crash with a segfault, not a python exception, so there is definitely something wrong with WxPython.
#The case seems to happen when SimpleHtmlListBox parse the HTML with the <img> tag #The case seems to happen when SimpleHtmlListBox parse the HTML with the <img> tag
@ -138,7 +144,7 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
return html return html
def clear_contacts(self): def clearContacts(self):
"""Clear all the contact list""" """Clear all the contact list"""
self.Clear() self.Clear()
@ -146,7 +152,9 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
"""add a contact to the list""" """add a contact to the list"""
debug (_("adding %s"),contact) debug (_("adding %s"),contact)
if not groups: if not groups:
idx = self.Insert(self.__presentItem(contact), 0, contact) _present = self.__presentItem(contact)
if _present:
idx = self.Insert(_present, 0, contact)
else: else:
for group in groups: for group in groups:
indexes = self.__find_idx(group) indexes = self.__find_idx(group)
@ -156,7 +164,9 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
else: else:
gp_idx = indexes[0] gp_idx = indexes[0]
self.Insert(self.__presentItem(contact), gp_idx+1, contact) _present = self.__presentItem(contact)
if _present:
self.Insert(_present, gp_idx+1, contact)
@ -176,11 +186,13 @@ class ContactList(wx.SimpleHtmlListBox, QuickContactList):
group = self.GetClientData(self.GetSelection()) group = self.GetClientData(self.GetSelection())
erased = self.__eraseGroup(group) erased = self.__eraseGroup(group)
if not erased: #the group was already erased, we can add again the contacts if not erased: #the group was already erased, we can add again the contacts
contacts = self.CM.getContFromGroup(group) contacts = [JID(contact) for contact in self.host.bridge.getContactsFromGroup(group, self.host.profile)]
contacts.sort() contacts.sort()
id_insert = self.GetSelection()+1 id_insert = self.GetSelection()+1
for contact in contacts: for contact in contacts:
self.Insert(self.__presentItem(contact), id_insert, contact) _present = self.__presentItem(contact)
if _present:
self.Insert(_present, id_insert, contact)
self.SetSelection(wx.NOT_FOUND) self.SetSelection(wx.NOT_FOUND)
self.ScrollToLine(first_visible) self.ScrollToLine(first_visible)
event.Skip(False) event.Skip(False)

View file

@ -64,17 +64,16 @@ class MainWindow(wx.Frame, QuickApp):
def __init__(self): def __init__(self):
QuickApp.__init__(self) QuickApp.__init__(self)
wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500)) wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500))
self.CM = QuickContactManagement() #FIXME: not the best place
#sizer #sizer
self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer) self.SetSizer(self.sizer)
#Frame elements #Frame elements
self.contactList = ContactList(self, self) self.contact_list = ContactList(self, self)
self.contactList.registerActivatedCB(self.onContactActivated) self.contact_list.registerActivatedCB(self.onContactActivated)
self.contactList.Hide() self.contact_list.Hide()
self.sizer.Add(self.contactList, 1, flag=wx.EXPAND) self.sizer.Add(self.contact_list, 1, flag=wx.EXPAND)
self.chat_wins=ChatList(self) self.chat_wins=ChatList(self)
self.CreateStatusBar() self.CreateStatusBar()
@ -120,7 +119,7 @@ class MainWindow(wx.Frame, QuickApp):
"""Hide profile panel then plug profile""" """Hide profile panel then plug profile"""
debug (_('plugin profile %s' % profile_key)) debug (_('plugin profile %s' % profile_key))
self.profile_pan.Hide() self.profile_pan.Hide()
self.contactList.Show() self.contact_list.Show()
self.sizer.Layout() self.sizer.Layout()
for i in range(self.menuBar.GetMenuCount()): for i in range(self.menuBar.GetMenuCount()):
self.menuBar.EnableTop(i, True) self.menuBar.EnableTop(i, True)
@ -410,7 +409,7 @@ class MainWindow(wx.Frame, QuickApp):
def onRemoveContact(self, e): def onRemoveContact(self, e):
debug(_("Remove contact request")) debug(_("Remove contact request"))
target = self.contactList.getSelection() target = self.contact_list.getSelection()
if not target: if not target:
dlg = wx.MessageDialog(self, _("You haven't selected any contact !"), dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
_('Error'), _('Error'),
@ -433,7 +432,7 @@ class MainWindow(wx.Frame, QuickApp):
def onShowProfile(self, e): def onShowProfile(self, e):
debug(_("Show contact's profile request")) debug(_("Show contact's profile request"))
target = self.contactList.getSelection() target = self.contact_list.getSelection()
if not target: if not target:
dlg = wx.MessageDialog(self, _("You haven't selected any contact !"), dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
_('Error'), _('Error'),

View file

@ -255,6 +255,12 @@ class DbusObject(dbus.service.Object):
def getContacts(self, profile_key="@DEFAULT@"): def getContacts(self, profile_key="@DEFAULT@"):
return self._callback("getContacts", unicode(profile_key)) return self._callback("getContacts", unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ss', out_signature='as',
async_callbacks=None)
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ssib', out_signature='a(dsss)', in_signature='ssib', out_signature='a(dsss)',
async_callbacks=('callback', 'errback')) async_callbacks=('callback', 'errback'))
@ -366,7 +372,7 @@ class DbusObject(dbus.service.Object):
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='sssss', out_signature='', in_signature='sssss', out_signature='',
async_callbacks=None) async_callbacks=None)
def sendMessage(self, to_jid, message, subject='', mess_type="chat", profile_key="@DEFAULT@"): def sendMessage(self, to_jid, message, subject='', mess_type="auto", profile_key="@DEFAULT@"):
return self._callback("sendMessage", unicode(to_jid), unicode(message), unicode(subject), unicode(mess_type), unicode(profile_key)) return self._callback("sendMessage", unicode(to_jid), unicode(message), unicode(subject), unicode(mess_type), unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,

View file

@ -271,6 +271,17 @@ doc_return=array of tuples with the following values:
- list of attributes as in [newContact] - list of attributes as in [newContact]
- groups where the contact is - groups where the contact is
[getContactsFromGroup]
type=method
category=core
sig_in=ss
sig_out=as
param_1_default="@DEFAULT@"
doc=Return informations about all contacts
doc_param_0=group: name of the group to check
doc_param_1=%(doc_profile_key)s
doc_return=array of jids
[getLastResource] [getLastResource]
type=method type=method
category=core category=core

View file

@ -30,3 +30,6 @@ class ConnectedProfileError(Exception):
class UnknownEntityError(Exception): class UnknownEntityError(Exception):
pass pass
class UnknownGroupError(Exception):
pass

View file

@ -126,6 +126,7 @@ class SAT(service.Service):
self.bridge.register("asyncConnect", self.asyncConnect) self.bridge.register("asyncConnect", self.asyncConnect)
self.bridge.register("disconnect", self.disconnect) self.bridge.register("disconnect", self.disconnect)
self.bridge.register("getContacts", self.getContacts) self.bridge.register("getContacts", self.getContacts)
self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
self.bridge.register("getLastResource", self.memory.getLastResource) self.bridge.register("getLastResource", self.memory.getLastResource)
self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus) self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus)
self.bridge.register("getWaitingSub", self.memory.getWaitingSub) self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
@ -276,8 +277,7 @@ class SAT(service.Service):
def getContacts(self, profile_key): def getContacts(self, profile_key):
client = self.getClient(profile_key) client = self.getClient(profile_key)
if not client: if not client:
error(_('Asking contacts for a non-existant profile')) raise ProfileUnknownError(_('Asking contacts for a non-existant profile'))
return []
ret = [] ret = []
for item in client.roster.getItems(): #we get all item for client's roster for item in client.roster.getItems(): #we get all item for client's roster
#and convert them to expected format #and convert them to expected format
@ -285,6 +285,12 @@ class SAT(service.Service):
ret.append([item.jid.userhost(), attr, item.groups]) ret.append([item.jid.userhost(), attr, item.groups])
return ret return ret
def getContactsFromGroup(self, group, profile_key):
client = self.getClient(profile_key)
if not client:
raise ProfileUnknownError(_("Asking group's contacts for a non-existant profile"))
return client.roster.getJidsFromGroup(group)
def purgeClient(self, profile): def purgeClient(self, profile):
"""Remove reference to a profile client and purge cache """Remove reference to a profile client and purge cache
the garbage collector can then free the memory""" the garbage collector can then free the memory"""

View file

@ -23,6 +23,7 @@ from twisted.internet import task, defer
from twisted.words.protocols.jabber import jid, xmlstream from twisted.words.protocols.jabber import jid, xmlstream
from wokkel import client, disco, xmppim, generic, compat from wokkel import client, disco, xmppim, generic, compat
from logging import debug, info, error from logging import debug, info, error
from sat.core import exceptions
class SatXMPPClient(client.XMPPClient): class SatXMPPClient(client.XMPPClient):
@ -164,9 +165,9 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
"""Return dictionary of attributes as used in bridge from a RosterItem """Return dictionary of attributes as used in bridge from a RosterItem
@param item: RosterItem @param item: RosterItem
@return: dictionary of attributes""" @return: dictionary of attributes"""
item_attr = {'to': str(item.subscriptionTo), item_attr = {'to': unicode(item.subscriptionTo),
'from': str(item.subscriptionFrom), 'from': unicode(item.subscriptionFrom),
'ask': str(item.ask) 'ask': unicode(item.ask)
} }
if item.name: if item.name:
item_attr['name'] = item.name item_attr['name'] = item.name
@ -236,6 +237,12 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
"""Return all items of the roster""" """Return all items of the roster"""
return self._jids.values() return self._jids.values()
def getJidsFromGroup(self, group):
try:
return self._groups[group]
except KeyError:
return exceptions.UnknownGroupError
class SatPresenceProtocol(xmppim.PresenceClientProtocol): class SatPresenceProtocol(xmppim.PresenceClientProtocol):

View file

@ -55,7 +55,7 @@ class TriggerManager:
"""This put a trigger point """This put a trigger point
All the trigger for that point will be run All the trigger for that point will be run
@param point_name: name of the trigger point @param point_name: name of the trigger point
@return: True if the action must be continue, False else""" @return: True if the action must be continued, False else"""
if not self.__triggers.has_key(point_name): if not self.__triggers.has_key(point_name):
return True return True