# HG changeset patch # User Goffi # Date 1424711767 -3600 # Node ID 71abccd8d22897e0767ffb485e8afc9bcfaefc6a # Parent 57a651a5b31dcd4185f721991e2016fb1bf5469a browser side: contact_list update: - removed ContactPanel's "add" and "remove" method, and replaced them by a display which display all needed contact at once - first naive implementation of display: if the display change, clear and add all ContactBox. Need to be done in a more efficient way in the future - ContactBox are never deleted, and only hidden when not displayed. ContactBox are automatically created on first use (TODO: add a way to delete them for entities not in roster) diff -r 57a651a5b31d -r 71abccd8d228 src/browser/sat_browser/contact_list.py --- a/src/browser/sat_browser/contact_list.py Mon Feb 23 18:14:07 2015 +0100 +++ b/src/browser/sat_browser/contact_list.py Mon Feb 23 18:16:07 2015 +0100 @@ -131,13 +131,13 @@ class ContactBox(VerticalPanel, ClickHandler, base_widget.DragLabel): - def __init__(self, host, jid_, name=None, click_listener=None, handle_menu=None): + def __init__(self, host, jid_, name=None, on_click=None, handle_menu=None): """ - @param host (SatWebFrontend) + @param host (SatWebFrontend): %(doc_host)s @param jid_ (jid.JID): contact JID @param name (unicode): contact alias - @param click_listener (callable): click callback + @param on_click (callable): click callback @param handle_menu (bool): if True, bind a popup menu to the avatar """ VerticalPanel.__init__(self, StyleName='contactBox', VerticalAlignment='middle') @@ -148,10 +148,10 @@ self.updateAvatar(host.getAvatarURL(jid_)) self.add(self.avatar) self.add(self.label) - if click_listener: + if on_click: ClickHandler.__init__(self) self.addClickListener(self) - self.click_listener = click_listener + self._on_click = on_click def addMenus(self, menu_bar): menu_bar.addCachedMenus(C.MENU_ROSTER_JID_CONTEXT, {'jid': unicode(self.jid)}) @@ -171,7 +171,7 @@ self.avatar.setUrl(url) def onClick(self, sender): - self.click_listener(self.jid) + self._on_click(self.jid) class GroupPanel(VerticalPanel): @@ -220,14 +220,15 @@ class BaseContactsPanel(VerticalPanel): - """Class that can be used to represent a contact list, but not necessarily - the one that is displayed on the left side. Special features like popup menu - panel or changing the contact states must be done in a sub-class.""" + """ContactList graphic representation + + Special features like popup menu panel or changing the contact states must be done in a sub-class. + """ def __init__(self, host, handle_click=False, handle_menu=False): VerticalPanel.__init__(self) self.host = host - self.contacts = [] + self._contacts = {} # entity jid to ContactBox map self.click_listener = None self.handle_menu = handle_menu @@ -236,48 +237,47 @@ host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE) self.click_listener = cb - def add(self, jid_, name=None): - """Add a contact to the list. + def display(self, jids): + """Display a contact in the list. - @param jid_ (jid.JID): jid_ of the contact + @param jids (list[jid.JID]): jids to display (the order is kept) @param name (unicode): optional name of the contact """ - assert isinstance(jid_, jid.JID) - if jid_ in self.contacts: + # FIXME: we do a full clear and add boxes after, we should only remove recently hidden boxes and add new ones, and re-order + current = [box.jid for box in self.children if isinstance(box, ContactBox)] + if current == jids: + # the display doesn't change return - index = 0 - for contact_ in self.contacts: - if contact_ > jid_: - break - index += 1 - self.contacts.insert(index, jid_) - box = ContactBox(self.host, jid_, name, self.click_listener, self.handle_menu) - VerticalPanel.insert(self, box, index) - - def remove(self, jid_): - box = self.getContactBox(jid_) - if not box: - return - VerticalPanel.remove(self, box) - self.contacts.remove(jid_) + self.clear() + for jid_ in jids: + assert isinstance(jid_, jid.JID) + box = self.getContactBox(jid_) + VerticalPanel.append(self, box) def isContactPresent(self, contact_jid): """Return True if a contact is present in the panel""" - return contact_jid in self.contacts + return contact_jid in self._contacts def getContacts(self): - return self.contacts + return self._contacts - def getContactBox(self, contact_jid): - """get the widget of a contact + def getContactBox(self, contact_jid, name=None, on_click=None, handle_menu=None): + """get the Contactbox of a contact + if the Contactbox doesn't exists, it will be created @param contact_jid (jid.JID): the contact - @return: ContactBox instance if present, else None""" + @param name (unicode): contact alias (used if ContactBox is created) + @param on_click (callable): click callback (used if ContactBox is created) + @param handle_menu (bool): if True, bind a popup menu to the avatar (used if ContactBox is created) + @return: ContactBox instance + """ assert isinstance(contact_jid, jid.JID) - for wid in self: - if isinstance(wid, ContactBox) and wid.jid == contact_jid: - return wid - return None + try: + return self._contacts[contact_jid] + except KeyError: + box = ContactBox(self.host, contact_jid, name, on_click, handle_menu) + self._contacts[contact_jid] = box + return box def updateAvatar(self, jid_, url): """Update the avatar of the given contact @@ -308,15 +308,12 @@ C.PRESENCE_UNAVAILABLE or None if not connected, else presence like RFC6121 #4.7.2.1""" assert type_ in ('availability', 'messageWaiting') contact_box = self.getContactBox(jid_) - if not contact_box: - log.warning("No contact box found for {}".format(jid_)) - else: - if type_ == 'availability': - if state is None: - state = C.PRESENCE_UNAVAILABLE - setPresenceStyle(contact_box.label, state) - elif type_ == 'messageWaiting': - contact_box.setMessageWaiting(state) + if type_ == 'availability': + if state is None: + state = C.PRESENCE_UNAVAILABLE + setPresenceStyle(contact_box.label, state) + elif type_ == 'messageWaiting': + contact_box.setMessageWaiting(state) class ContactTitleLabel(base_widget.DragLabel, Label, ClickHandler): @@ -380,15 +377,13 @@ self._group_panel.remove(group) ### JIDS ### - current_contacts = set([jid_ for jid_ in self._cache.keys() if self.entityToShow(jid_)]) - shown_contacts = set(self._contacts_panel.getContacts()) - new_contacts = current_contacts.difference(shown_contacts) - removed_contacts = shown_contacts.difference(current_contacts) + to_show = [jid_ for jid_ in self._cache.keys() if self.entityToShow(jid_)] + to_show.sort() - for contact in new_contacts: - self._contacts_panel.add(contact) - for contact in removed_contacts: - self._contacts_panel.remove(contact) + self._contacts_panel.display(to_show) + + for jid_ in self._alerts: + self._contacts_panel.setState(jid_, "messageWaiting", True) def onWindowResized(self, width, height): contact_panel_elt = self.getElement() @@ -480,10 +475,12 @@ # self.updateVisibility([jid_s], self.getContactGroups(jid_s)) def setContactMessageWaiting(self, jid, waiting): - """Show an visual indicator that contact has send a message + """Show a visual indicator that contact has send a message + @param jid: jid of the contact @param waiting: True if message are waiting""" - self._contacts_panel.setState(jid, "messageWaiting", waiting) + raise Exception("Should not be there") + # self._contacts_panel.setState(jid, "messageWaiting", waiting) # def getConnected(self, filter_muc=False): # """return a list of all jid (bare jid) connected @@ -596,6 +593,7 @@ entity_bare = entity.bare show = self.getCache(entity_bare, C.PRESENCE_SHOW) # we use cache to have the show nformation of main resource only self._contacts_panel.setState(entity_bare, "availability", show) + self.update() # def updateVisibility(self, jids, groups): # """Set the widgets visibility for the given contacts and groups