Mercurial > libervia-backend
diff frontends/src/primitivus/contact_list.py @ 1367:f71a0fc26886
merged branch frontends_multi_profiles
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 18 Mar 2015 10:52:28 +0100 |
parents | 1679ac59f701 |
children | 017270e6eea4 |
line wrap: on
line diff
--- a/frontends/src/primitivus/contact_list.py Thu Feb 05 11:59:26 2015 +0100 +++ b/frontends/src/primitivus/contact_list.py Wed Mar 18 10:52:28 2015 +0100 @@ -21,36 +21,25 @@ import urwid from urwid_satext import sat_widgets from sat_frontends.quick_frontend.quick_contact_list import QuickContactList -from sat_frontends.quick_frontend.quick_utils import unescapePrivate -from sat_frontends.tools.jid import JID from sat_frontends.primitivus.status import StatusBar from sat_frontends.primitivus.constants import Const as C from sat_frontends.primitivus.keys import action_key_map as a_key +from sat_frontends.primitivus.widget import PrimitivusWidget +from sat_frontends.tools import jid from sat.core import log as logging log = logging.getLogger(__name__) -class ContactList(urwid.WidgetWrap, QuickContactList): +class ContactList(PrimitivusWidget, QuickContactList): signals = ['click','change'] - def __init__(self, host, on_click=None, on_change=None, user_data=None): - QuickContactList.__init__(self) - self.host = host - self.selected = None - self.groups={} - self.alert_jid=set() - self.show_status = False - self.show_disconnected = False - self.show_empty_groups = True - # TODO: this may lead to two successive UI refresh and needs an optimization - self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS, "General", profile_key=host.profile, callback=self.showEmptyGroups) - self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS, "General", profile_key=host.profile, callback=self.showOfflineContacts) + def __init__(self, host, on_click=None, on_change=None, user_data=None, profile=None): + QuickContactList.__init__(self, host, profile) #we now build the widget - self.host.status_bar = StatusBar(host) - self.frame = sat_widgets.FocusFrame(self.__buildList(), None, self.host.status_bar) - self.main_widget = sat_widgets.LabelLine(self.frame, sat_widgets.SurroundedText(_("Contacts"))) - urwid.WidgetWrap.__init__(self, self.main_widget) + self.status_bar = StatusBar(host) + self.frame = sat_widgets.FocusFrame(self._buildList(), None, self.status_bar) + PrimitivusWidget.__init__(self, self.frame, _(u'Contacts')) if on_click: urwid.connect_signal(self, 'click', on_click, user_data) if on_change: @@ -59,16 +48,14 @@ def update(self): """Update display, keep focus""" widget, position = self.frame.body.get_focus() - self.frame.body = self.__buildList() + self.frame.body = self._buildList() if position: try: self.frame.body.focus_position = position except IndexError: pass - self.host.redraw() - - def update_jid(self, jid): - self.update() + self._invalidate() + self.host.redraw() # FIXME: check if can be avoided def keypress(self, size, key): # FIXME: we have a temporary behaviour here: FOCUS_SWITCH change focus globally in the parent, @@ -81,18 +68,18 @@ self.show_status = not self.show_status self.update() elif key == a_key['DISCONNECTED_HIDE']: #user wants to (un)hide disconnected contacts - self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.str(not self.show_disconnected), "General", profile_key=self.host.profile) + self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.str(not self.show_disconnected), "General", profile_key=self.profile) + elif key == a_key['RESOURCES_HIDE']: #user wants to (un)hide contacts resources + self.showResources(not self.show_resources) + self.update() return super(ContactList, self).keypress(size, key) - def __contains__(self, jid): - for group in self.groups: - if jid.bare in self.groups[group][1]: - return True - return False + # modify the contact list def setFocus(self, text, select=False): """give focus to the first element that matches the given text. You can also pass in text a sat_frontends.tools.jid.JID (it's a subclass of unicode). + @param text: contact group name, contact or muc userhost, muc private dialog jid @param select: if True, the element is also clicked """ @@ -103,12 +90,8 @@ # contact group value = widget.getValue() elif isinstance(widget, sat_widgets.SelectableText): - if widget.data.startswith(C.PRIVATE_PREFIX): - # muc private dialog - value = widget.getValue() - else: - # contact or muc - value = widget.data + # contact or muc + value = widget.data else: # Divider instance continue @@ -116,243 +99,214 @@ if text.strip() == value.strip(): self.frame.body.focus_position = idx if select: - self.__contactClicked(widget, True) + self._contactClicked(False, widget, True) return except AttributeError: pass 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.bare) + log.debug(u"Not element found for {} in setFocus".format(text)) + + def specialResourceVisible(self, entity): + """Assure a resource of a special entity is visible and clickable + + Mainly used to display private conversation in MUC rooms + @param entity: full jid of the resource to show + """ + assert isinstance(entity, jid.JID) + if entity not in self._special_extras: + self._special_extras.add(entity) + self.update() + + # events + + def _groupClicked(self, group_wid): + group = group_wid.getValue() + data = self.getGroupData(group) + data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False) + self.setFocus(group) + self.update() + + def _contactClicked(self, use_bare_jid, contact_wid, selected): + """Method called when a contact is clicked + + @param use_bare_jid: True if use_bare_jid is set in self._buildEntityWidget. + If True, all jids in self._alerts with the same bare jid has contact_wid.data will be removed + @param contact_wid: widget of the contact, must have the entity set in data attribute + @param selected: boolean returned by the widget, telling if it is selected + """ + entity = contact_wid.data + if use_bare_jid: + to_remove = set() + for alert_entity in self._alerts: + if alert_entity.bare == entity.bare: + to_remove.add(alert_entity) + self._alerts.difference_update(to_remove) + else: + self._alerts.discard(entity) + self.host.modeHint(C.MODE_INSERTION) + self.update() + self._emit('click', entity) + + def onPresenceUpdate(self, entity, show, priority, statuses, profile): + super(ContactList, self).onPresenceUpdate(entity, show, priority, statuses, profile) + self.update() + + def onNickUpdate(self, entity, new_nick, profile): self.update() - def __groupClicked(self, group_wid): - group = self.groups[group_wid.getValue()] - group[0] = not group[0] - self.update() - self.setFocus(group_wid.getValue()) + # Methods to build the widget + + def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_alert=True, with_show_attr=True, markup_prepend=None, markup_append = None): + """Build one contact markup data - def __contactClicked(self, contact_wid, selected): - self.selected = contact_wid.data - for widget in self.frame.body.body: - if widget.__class__ == sat_widgets.SelectableText: - widget.setState(widget.data == self.selected, invisible=True) - if self.selected in self.alert_jid: - self.alert_jid.remove(self.selected) - self.host.modeHint('INSERTION') - self.update() - self._emit('click') + @param entity (jid.JID): entity to build + @param keys (iterable): value to markup, in preferred order. + The first available key will be used. + If key starts with "cache_", it will be checked in cache, + else, getattr will be done on entity with the key (e.g. getattr(entity, 'node')). + If nothing full or keys is None, full entity is used. + @param use_bare_jid (bool): if True, use bare jid for alerts and selected comparisons + @param with_alert (bool): if True, show alert if entity is in self._alerts + @param with_show_attr (bool): if True, show color corresponding to presence status + @param markup_prepend (list): markup to prepend to the generated one before building the widget + @param markup_append (list): markup to append to the generated one before building the widget + @return (list): markup data are expected by Urwid text widgets + """ + markup = [] + if use_bare_jid: + alerts = {entity.bare for entity in self._alerts} + selected = {entity.bare for entity in self._selected} + else: + alerts = self._alerts + selected = self._selected + if keys is None: + entity_txt = entity + else: + cache = self.getCache(entity) + for key in keys: + if key.startswith('cache_'): + entity_txt = cache.get(key[6:]) + else: + entity_txt = getattr(entity, key) + if entity_txt: + break + if not entity_txt: + entity_txt = entity - def __buildContact(self, content, contacts): - """Add contact representation in widget list + if with_show_attr: + show = self.getCache(entity, C.PRESENCE_SHOW) + if show is None: + show = C.PRESENCE_UNAVAILABLE + show_icon, entity_attr = C.PRESENCE.get(show, ('', 'default')) + markup.insert(0, u"{} ".format(show_icon)) + else: + entity_attr = 'default' + + if with_alert and entity in alerts: + entity_attr = 'alert' + header = C.ALERT_HEADER + else: + header = '' + + markup.append((entity_attr, entity_txt)) + if markup_prepend: + markup.insert(0, markup_prepend) + if markup_append: + markup.extend(markup_append) + + widget = sat_widgets.SelectableText(markup, + selected = entity in selected, + header = header) + widget.data = entity + widget.comp = entity_txt.lower() # value to use for sorting + urwid.connect_signal(widget, 'change', self._contactClicked, user_args=[use_bare_jid]) + return widget + + def _buildEntities(self, content, entities): + """Add entity representation in widget list + @param content: widget list, e.g. SimpleListWalker - @param contacts (list): list of JID userhosts""" - if not contacts: + @param entities (iterable): iterable of JID to display + """ + if not entities: return widgets = [] # list of built widgets - for contact in contacts: - if contact.startswith(C.PRIVATE_PREFIX): - contact_disp = ('alert' if contact in self.alert_jid else "show_normal", unescapePrivate(contact)) - show_icon = '' - status = '' + for entity in entities: + if entity in self._specials or not self.entityToShow(entity): + continue + markup_extra = [] + if self.show_resources: + for resource in self.getCache(entity, C.CONTACT_RESOURCES): + resource_disp = ('resource_main' if resource == self.getCache(entity, C.CONTACT_MAIN_RESOURCE) else 'resource', "\n " + resource) + markup_extra.append(resource_disp) + if self.show_status: + status = self.getCache(jid.JID('%s/%s' % (entity, resource)), 'status') + status_disp = ('status', "\n " + status) if status else "" + markup_extra.append(status_disp) + + else: - jid = JID(contact) - name = self.getCache(jid, 'name') - nick = self.getCache(jid, 'nick') - status = self.getCache(jid, 'status') - show = self.getCache(jid, 'show') - if show is None: - show = "unavailable" - if not self.contactToShow(contact): - continue - show_icon, show_attr = C.PRESENCE.get(show, ('', 'default')) - contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.bare) - display = [show_icon + " ", contact_disp] - if self.show_status: - status_disp = ('status', "\n " + status) if status else "" - display.append(status_disp) - header = '(*) ' if contact in self.alert_jid else '' - widget = sat_widgets.SelectableText(display, - selected=contact == self.selected, - header=header) - widget.data = contact - widget.comp = contact_disp[1].lower() # value to use for sorting + if self.show_status: + status = self.getCache(entity, 'status') + status_disp = ('status', "\n " + status) if status else "" + markup_extra.append(status_disp) + widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), use_bare_jid=True, markup_append=markup_extra) widgets.append(widget) widgets.sort(key=lambda widget: widget.comp) for widget in widgets: content.append(widget) - urwid.connect_signal(widget, 'change', self.__contactClicked) - def __buildSpecials(self, content): + def _buildSpecials(self, content): """Build the special entities""" - specials = self.specials.keys() + specials = list(self._specials) specials.sort() - for special in specials: - jid=JID(special) - name = self.getCache(jid, 'name') - nick = self.getCache(jid, 'nick') - special_disp = ('alert' if special in self.alert_jid else 'default', nick or name or jid.node or jid.bare) - display = [ " " , special_disp] - header = '(*) ' if special in self.alert_jid else '' - widget = sat_widgets.SelectableText(display, - selected = special==self.selected, - header=header) - widget.data = special + extra_shown = set() + for entity in specials: + # the special widgets + widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), with_show_attr=False) content.append(widget) - urwid.connect_signal(widget, 'change', self.__contactClicked) - def __buildList(self): + # resources which must be displayed (e.g. MUC private conversations) + extras = [extra for extra in self._special_extras if extra.bare == entity.bare] + extras.sort() + for extra in extras: + widget = self._buildEntityWidget(extra, ('resource',), markup_prepend = ' ') + content.append(widget) + extra_shown.add(extra) + + # entities which must be visible but not resource of current special entities + for extra in self._special_extras.difference(extra_shown): + widget = self._buildEntityWidget(extra, ('resource',)) + content.append(widget) + + def _buildList(self): """Build the main contact list widget""" content = urwid.SimpleListWalker([]) - self.__buildSpecials(content) - if self.specials: + self._buildSpecials(content) + if self._specials: content.append(urwid.Divider('=')) - group_keys = self.groups.keys() - group_keys.sort(key=lambda x: x.lower() if x else x) - for key in group_keys: - unfolded = self.groups[key][0] - contacts = list(self.groups[key][1]) - if key is not None and (self.nonEmptyGroup(contacts) or self.show_empty_groups): - header = '[-]' if unfolded else '[+]' - widget = sat_widgets.ClickableText(key, header=header + ' ') + groups = list(self._groups) + groups.sort(key=lambda x: x.lower() if x else x) + for group in groups: + data = self.getGroupData(group) + folded = data.get(C.GROUP_DATA_FOLDED, False) + jids = list(data['jids']) + if group is not None and (self.anyEntityToShow(jids) or self.show_empty_groups): + header = '[-]' if not folded else '[+]' + widget = sat_widgets.ClickableText(group, header=header + ' ') content.append(widget) - urwid.connect_signal(widget, 'click', self.__groupClicked) - if unfolded: - self.__buildContact(content, contacts) - return urwid.ListBox(content) - - def contactToShow(self, contact): - """Tell if the contact should be showed or hidden. - - @param contact (str): JID userhost of the contact - @return: True if that contact should be showed in the list""" - show = self.getCache(JID(contact), 'show') - return (show is not None and show != "unavailable") or \ - self.show_disconnected or contact in self.alert_jid or contact == self.selected - - def nonEmptyGroup(self, contacts): - """Tell if a contact group contains some contacts to show. - - @param contacts (list[str]): list of JID userhosts - @return: bool - """ - for contact in contacts: - if self.contactToShow(contact): - return True - return False - - def unselectAll(self): - """Unselect all contacts""" - self.selected = None - for widget in self.frame.body.body: - if widget.__class__ == sat_widgets.SelectableText: - widget.setState(False, invisible=True) - - def getContact(self): - """Return contact currently selected""" - return self.selected - - def clearContacts(self): - """clear all the contact list""" - QuickContactList.clearContacts(self) - self.groups={} - self.selected = None - self.unselectAll() - self.update() - - def replace(self, jid, groups=None, attributes=None): - """Add a contact to the list if doesn't exist, else update it. - - This method can be called with groups=None for the purpose of updating - the contact's attributes (e.g. nickname). In that case, the groups - attribute must not be set to the default group but ignored. If not, - you may move your contact from its actual group(s) to the default one. - - None value for 'groups' has a different meaning than [None] which is for the default group. + urwid.connect_signal(widget, 'click', self._groupClicked) + if not folded: + self._buildEntities(content, jids) + not_in_roster = set(self._cache).difference(self._roster).difference(self._specials).difference((self.whoami.bare,)) + if not_in_roster: + content.append(urwid.Divider('-')) + self._buildEntities(content, not_in_roster) - @param jid (JID) - @param groups (list): list of groups or None to ignore the groups membership. - @param attributes (dict) - """ - QuickContactList.replace(self, jid, groups, attributes) # eventually change the nickname - if jid.bare in self.specials: - return - if groups is None: - self.update() - return - assert isinstance(jid, JID) - assert isinstance(groups, list) - if groups == []: - groups = [None] # [None] is the default group - for group in [group for group in self.groups if group not in groups]: - try: # remove the contact from a previous group - self.groups[group][1].remove(jid.bare) - except KeyError: - pass - for group in groups: - if group not in self.groups: - self.groups[group] = [True, set()] # [unfold, list_of_contacts] - self.groups[group][1].add(jid.bare) - self.update() - - def remove(self, jid): - """remove a contact from the list""" - QuickContactList.remove(self, jid) - groups_to_remove = [] - for group in self.groups: - contacts = self.groups[group][1] - if jid.bare in contacts: - contacts.remove(jid.bare) - if not len(contacts): - groups_to_remove.append(group) - for group in groups_to_remove: - del self.groups[group] - self.update() - - def add(self, jid, param_groups=None): - """add a contact to the list""" - self.replace(jid, param_groups if param_groups else [None]) - - def setSpecial(self, special_jid, special_type, show=False): - """Set entity as a special - @param special_jid: jid of the entity - @param special_type: special type (e.g.: "MUC") - @param show: True to display the dialog to chat with this entity - """ - QuickContactList.setSpecial(self, special_jid, special_type, show) - if None in self.groups: - folded, group_jids = self.groups[None] - for group_jid in group_jids: - if JID(group_jid).bare == special_jid.bare: - group_jids.remove(group_jid) - break - self.update() - if show: - # also display the dialog for this room - self.setFocus(special_jid, True) - self.host.redraw() - - def updatePresence(self, jid, show, priority, statuses): - #XXX: for the moment, we ignore presence updates for special entities - if jid.bare not in self.specials: - QuickContactList.updatePresence(self, jid, show, priority, statuses) - - def showOfflineContacts(self, show): - show = C.bool(show) - if self.show_disconnected == show: - return - self.show_disconnected = show - self.update() - - def showEmptyGroups(self, show): - show = C.bool(show) - if self.show_empty_groups == show: - return - self.show_empty_groups = show - self.update() + return urwid.ListBox(content)