Mercurial > libervia-backend
view frontends/src/primitivus/contact_list.py @ 2182:087eec4c6c07
memory (persistent, sqlite): better private values handling + new LazyPersistentBinaryDict:
- merged private values method handling in sqlite, and added a keys arguments to get only some keys
- LazyPersistentBinaryDict allow to get values in database only when they are needed, saving memory for big data
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 12 Mar 2017 19:33:17 +0100 |
parents | 35762e9ce8b9 |
children | 8b37a62336c3 |
line wrap: on
line source
#!/usr/bin/env python2 # -*- coding: utf-8 -*- # Primitivus: a SAT frontend # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # 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/>. from sat.core.i18n import _ import urwid from urwid_satext import sat_widgets from sat_frontends.quick_frontend.quick_contact_list import QuickContactList 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__) from sat_frontends.quick_frontend import quick_widgets class ContactList(PrimitivusWidget, QuickContactList): PROFILES_MULTIPLE=False PROFILES_ALLOW_NONE=False signals = ['click','change'] # FIXME: Only single profile is managed so far def __init__(self, host, target, on_click=None, on_change=None, user_data=None, profiles=None): QuickContactList.__init__(self, host, profiles) self.contact_list = self.host.contact_lists[self.profile] #we now build the 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: urwid.connect_signal(self, 'change', on_change, user_data) self.host.addListener('notification', self.onNotification, [self.profile]) self.host.addListener('notificationsClear', self.onNotification, [self.profile]) self.postInit() def update(self, entities=None, type_=None, profile=None): """Update display, keep focus""" # FIXME: full update is done each time, must handle entities, type_ and profile widget, position = self.frame.body.get_focus() self.frame.body = self._buildList() if position: try: self.frame.body.focus_position = position except IndexError: pass 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, # and FOCUS_UP/DOWN is transwmitter to parent if we are respectively on the first or last element if key in sat_widgets.FOCUS_KEYS: if (key == a_key['FOCUS_SWITCH'] or (key == a_key['FOCUS_UP'] and self.frame.focus_position == 'body') or (key == a_key['FOCUS_DOWN'] and self.frame.focus_position == 'footer')): return key if key == a_key['STATUS_HIDE']: #user wants to (un)hide contacts' statuses self.contact_list.show_status = not self.contact_list.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.boolConst(not self.contact_list.show_disconnected), "General", profile_key=self.profile) elif key == a_key['RESOURCES_HIDE']: #user wants to (un)hide contacts resources self.contact_list.showResources(not self.contact_list.show_resources) self.update() return super(ContactList, self).keypress(size, key) # QuickWidget methods @staticmethod def getWidgetHash(target, profiles): profiles = sorted(profiles) return tuple(profiles) # 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 """ idx = 0 for widget in self.frame.body.body: try: if isinstance(widget, sat_widgets.ClickableText): # contact group value = widget.getValue() elif isinstance(widget, sat_widgets.SelectableText): # contact or muc value = widget.data else: # Divider instance continue # there's sometimes a leading space if text.strip() == value.strip(): self.frame.body.focus_position = idx if select: self._contactClicked(False, widget, True) return except AttributeError: pass idx += 1 log.debug(u"Not element found for {} in setFocus".format(text)) # events def _groupClicked(self, group_wid): group = group_wid.getValue() data = self.contact_list.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. @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 self.host.modeHint(C.MODE_INSERTION) self._emit('click', entity) def onNotification(self, entity, notif, profile): notifs = list(self.host.getNotifs(C.ENTITY_ALL, profile=self.profile)) if notifs: self.title_dynamic = u"({})".format(len(notifs)) else: self.title_dynamic = None self.host.redraw() # FIXME: should not be necessary # Methods to build the widget def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_notifs=True, with_show_attr=True, markup_prepend=None, markup_append=None, special=False): """Build one contact markup data @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 selected comparisons @param with_notifs (bool): if True, show notification count @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 @param special (bool): True if entity is a special one @return (list): markup data are expected by Urwid text widgets """ markup = [] if use_bare_jid: selected = {entity.bare for entity in self.contact_list._selected} else: selected = self.contact_list._selected if keys is None: entity_txt = entity else: cache = self.contact_list.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 if with_show_attr: show = self.contact_list.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' notifs = list(self.host.getNotifs(entity, exact_jid=special, profile=self.profile)) if notifs: header = [('cl_notifs', u'({})'.format(len(notifs))), u' '] if list(self.host.getNotifs(entity.bare, C.NOTIFY_MENTION, profile=self.profile)): header = ('cl_mention', header) else: header = u'' 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 entities (iterable): iterable of JID to display """ if not entities: return widgets = [] # list of built widgets for entity in entities: if entity in self.contact_list._specials or not self.contact_list.entityToShow(entity): continue markup_extra = [] if self.contact_list.show_resources: for resource in self.contact_list.getCache(entity, C.CONTACT_RESOURCES): resource_disp = ('resource_main' if resource == self.contact_list.getCache(entity, C.CONTACT_MAIN_RESOURCE) else 'resource', "\n " + resource) markup_extra.append(resource_disp) if self.contact_list.show_status: status = self.contact_list.getCache(jid.JID('%s/%s' % (entity, resource)), 'status') status_disp = ('status', "\n " + status) if status else "" markup_extra.append(status_disp) else: if self.contact_list.show_status: status = self.contact_list.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) def _buildSpecials(self, content): """Build the special entities""" specials = sorted(self.contact_list.getSpecials()) current = None for entity in specials: if current is not None and current.bare == entity.bare: # nested entity (e.g. MUC private conversations) widget = self._buildEntityWidget(entity, ('resource',), markup_prepend=' ', special=True) else: # the special widgets if entity.resource: widget = self._buildEntityWidget(entity, ('resource',), special=True) else: widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), with_show_attr=False, special=True) content.append(widget) def _buildList(self): """Build the main contact list widget""" content = urwid.SimpleListWalker([]) self._buildSpecials(content) if self.contact_list._specials: content.append(urwid.Divider('=')) groups = list(self.contact_list._groups) groups.sort(key=lambda x: x.lower() if x else x) for group in groups: data = self.contact_list.getGroupData(group) folded = data.get(C.GROUP_DATA_FOLDED, False) jids = list(data['jids']) if group is not None and (self.contact_list.anyEntityToShow(jids) or self.contact_list.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 not folded: self._buildEntities(content, jids) not_in_roster = set(self.contact_list._cache).difference(self.contact_list._roster).difference(self.contact_list._specials).difference((self.contact_list.whoami.bare,)) if not_in_roster: content.append(urwid.Divider('-')) self._buildEntities(content, not_in_roster) return urwid.ListBox(content) quick_widgets.register(QuickContactList, ContactList)