quick frontend, primitivus: quickContactList refactored to handle several profiles at once
This commit is contained in:
parent
0b52799489
commit
8851addc20
6 changed files with 552 additions and 168 deletions
|
|
@ -28,13 +28,18 @@ 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, on_click=None, on_change=None, user_data=None, profile=None):
|
||||
QuickContactList.__init__(self, host, profile)
|
||||
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)
|
||||
|
|
@ -45,8 +50,9 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
if on_change:
|
||||
urwid.connect_signal(self, 'change', on_change, user_data)
|
||||
|
||||
def update(self):
|
||||
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:
|
||||
|
|
@ -65,15 +71,22 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
(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.show_status = not self.show_status
|
||||
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.show_disconnected), "General", profile_key=self.profile)
|
||||
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.showResources(not self.show_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):
|
||||
|
|
@ -122,7 +135,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
|
||||
def _groupClicked(self, group_wid):
|
||||
group = group_wid.getValue()
|
||||
data = self.getGroupData(group)
|
||||
data = self.contact_list.getGroupData(group)
|
||||
data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False)
|
||||
self.setFocus(group)
|
||||
self.update()
|
||||
|
|
@ -136,13 +149,10 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
@param selected: boolean returned by the widget, telling if it is selected
|
||||
"""
|
||||
entity = contact_wid.data
|
||||
self.removeAlerts(entity, use_bare_jid)
|
||||
self.contact_list.removeAlerts(entity, use_bare_jid)
|
||||
self.host.modeHint(C.MODE_INSERTION)
|
||||
self._emit('click', entity)
|
||||
|
||||
def onNickUpdate(self, entity, new_nick, profile):
|
||||
self.update()
|
||||
|
||||
# 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):
|
||||
|
|
@ -163,13 +173,13 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
"""
|
||||
markup = []
|
||||
if use_bare_jid:
|
||||
selected = {entity.bare for entity in self._selected}
|
||||
selected = {entity.bare for entity in self.contact_list._selected}
|
||||
else:
|
||||
selected = self._selected
|
||||
selected = self.contact_list._selected
|
||||
if keys is None:
|
||||
entity_txt = entity
|
||||
else:
|
||||
cache = self.getCache(entity)
|
||||
cache = self.contact_list.getCache(entity)
|
||||
for key in keys:
|
||||
if key.startswith('cache_'):
|
||||
entity_txt = cache.get(key[6:])
|
||||
|
|
@ -181,7 +191,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
entity_txt = entity
|
||||
|
||||
if with_show_attr:
|
||||
show = self.getCache(entity, C.PRESENCE_SHOW)
|
||||
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'))
|
||||
|
|
@ -189,7 +199,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
else:
|
||||
entity_attr = 'default'
|
||||
|
||||
alerts_count = self.getAlerts(entity, use_bare_jid=use_bare_jid)
|
||||
alerts_count = len(self.contact_list.getAlerts(entity, use_bare_jid=use_bare_jid))
|
||||
if with_alert and alerts_count:
|
||||
entity_attr = 'alert'
|
||||
header = C.ALERT_HEADER % alerts_count
|
||||
|
|
@ -221,22 +231,22 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
widgets = [] # list of built widgets
|
||||
|
||||
for entity in entities:
|
||||
if entity in self._specials or not self.entityToShow(entity):
|
||||
if entity in self.contact_list._specials or not self.contact_list.entityToShow(entity):
|
||||
continue
|
||||
markup_extra = []
|
||||
if self.show_resources:
|
||||
for resource in self.getCache(entity, C.CONTACT_RESOURCES):
|
||||
if self.contact_list.show_resources:
|
||||
for resource in self.contact_list.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')
|
||||
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.show_status:
|
||||
status = self.getCache(entity, 'status')
|
||||
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)
|
||||
|
|
@ -249,7 +259,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
|
||||
def _buildSpecials(self, content):
|
||||
"""Build the special entities"""
|
||||
specials = list(self._specials)
|
||||
specials = list(self.contact_list._specials)
|
||||
specials.sort()
|
||||
extra_shown = set()
|
||||
for entity in specials:
|
||||
|
|
@ -258,7 +268,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
content.append(widget)
|
||||
|
||||
# resources which must be displayed (e.g. MUC private conversations)
|
||||
extras = [extra for extra in self._special_extras if extra.bare == entity.bare]
|
||||
extras = [extra for extra in self.contact_list._special_extras if extra.bare == entity.bare]
|
||||
extras.sort()
|
||||
for extra in extras:
|
||||
widget = self._buildEntityWidget(extra, ('resource',), markup_prepend = ' ')
|
||||
|
|
@ -266,7 +276,7 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
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):
|
||||
for extra in self.contact_list._special_extras.difference(extra_shown):
|
||||
widget = self._buildEntityWidget(extra, ('resource',))
|
||||
content.append(widget)
|
||||
|
||||
|
|
@ -275,25 +285,27 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
content = urwid.SimpleListWalker([])
|
||||
|
||||
self._buildSpecials(content)
|
||||
if self._specials:
|
||||
if self.contact_list._specials:
|
||||
content.append(urwid.Divider('='))
|
||||
|
||||
groups = list(self._groups)
|
||||
groups = list(self.contact_list._groups)
|
||||
groups.sort(key=lambda x: x.lower() if x else x)
|
||||
for group in groups:
|
||||
data = self.getGroupData(group)
|
||||
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.anyEntityToShow(jids) or self.show_empty_groups):
|
||||
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._cache).difference(self._roster).difference(self._specials).difference((self.whoami.bare,))
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class EditBar(sat_widgets.ModalEdit):
|
|||
for params, see AdvancedEdit"""
|
||||
nicks = []
|
||||
for profile, clist in self.host.contact_lists.iteritems():
|
||||
for contact in clist.getContacts():
|
||||
for contact in clist.selected:
|
||||
chat = self.host.widgets.getWidget(quick_chat.QuickChat, contact, profile)
|
||||
if chat.type != C.CHAT_GROUP:
|
||||
continue
|
||||
|
|
@ -464,11 +464,6 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
self.main_widget = PrimitivusTopWidget(self.center_part, self.menu_roller, self.notif_bar, self.editBar)
|
||||
return self.main_widget
|
||||
|
||||
def addContactList(self, profile):
|
||||
contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw(), profile=profile)
|
||||
self.contact_lists_pile.contents.append((contact_list, ('weight', 1)))
|
||||
return contact_list
|
||||
|
||||
def plugging_profiles(self):
|
||||
self.loop.widget = self._buildMainWidget()
|
||||
self.redraw()
|
||||
|
|
@ -480,6 +475,12 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
else:
|
||||
del self._early_popup
|
||||
|
||||
def profilePlugged(self, profile):
|
||||
QuickApp.profilePlugged(self, profile)
|
||||
contact_list = self.widgets.getOrCreateWidget(ContactList, None, on_new_widget=None, on_click=self.contactSelected, on_change=lambda w: self.redraw(), profile=profile)
|
||||
self.contact_lists_pile.contents.append((contact_list, ('weight', 1)))
|
||||
return contact_list
|
||||
|
||||
def isHidden(self):
|
||||
"""Tells if the frontend window is hidden.
|
||||
|
||||
|
|
@ -555,10 +556,9 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
log.debug("No menu to delete")
|
||||
self.selected_widget = widget
|
||||
self._visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
|
||||
for contact_list in self.contact_lists.itervalues():
|
||||
contact_list.unselectAll()
|
||||
self.contact_lists.select(None)
|
||||
|
||||
for wid in self.visible_widgets:
|
||||
for wid in self.visible_widgets: # FIXME: check if widgets.getWidgets is not more appropriate
|
||||
if isinstance(wid, Chat):
|
||||
contact_list = self.contact_lists[wid.profile]
|
||||
contact_list.select(wid.target)
|
||||
|
|
@ -776,7 +776,11 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
#MISC CALLBACKS#
|
||||
|
||||
def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE):
|
||||
self.contact_lists[profile].status_bar.setPresenceStatus(show, status)
|
||||
contact_list_wid = self.widgets.getWidget(ContactList, profiles=profile)
|
||||
if contact_list_wid is not None:
|
||||
contact_list_wid.status_bar.setPresenceStatus(show, status)
|
||||
else:
|
||||
log.warning(u"No ContactList widget found for profile {}".format(profile))
|
||||
|
||||
sat = PrimitivusApp()
|
||||
sat.start()
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ class Const(constants.Const):
|
|||
CONTACT_MAIN_RESOURCE = 'main_resource'
|
||||
CONTACT_SPECIAL = 'special'
|
||||
CONTACT_SPECIAL_GROUP = 'group' # group chat special entity
|
||||
CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,) # set of allowed values for special flag
|
||||
CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE} # set of forbidden names for contact data
|
||||
CONTACT_SELECTED = 'selected'
|
||||
CONTACT_PROFILE = 'profile' # used in handler to track where the contact is coming from
|
||||
CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,) # allowed values for special flag
|
||||
CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE, CONTACT_SELECTED, CONTACT_PROFILE} # set of forbidden names for contact data
|
||||
|
||||
# Chats
|
||||
CHAT_ONE2ONE = 'one2one'
|
||||
|
|
@ -61,6 +63,10 @@ class Const(constants.Const):
|
|||
"paused": u"⦷"
|
||||
}
|
||||
|
||||
# Alerts
|
||||
ALERT_MESSAGE = "MESSAGE" # New message received
|
||||
ALERT_NICK = "NICK" # our nickname was mentionned
|
||||
|
||||
# Blogs
|
||||
ENTRY_MODE_TEXT = "text"
|
||||
ENTRY_MODE_RICH = "rich"
|
||||
|
|
@ -73,4 +79,11 @@ class Const(constants.Const):
|
|||
WIDGET_RAISE = 'RAISE'
|
||||
WIDGET_RECREATE = 'RECREATE'
|
||||
|
||||
# Updates (generic)
|
||||
UPDATE_DELETE = 'DELETE'
|
||||
UPDATE_MODIFY = 'MODIFY'
|
||||
UPDATE_ADD = 'ADD'
|
||||
UPDATE_SELECTION = 'SELECTION'
|
||||
UPDATE_STRUCTURE = 'STRUCTURE' # high level update (i.e. not item level but organisation of items)
|
||||
|
||||
LISTENERS = {'avatar', 'nick', 'presence', 'profilePlugged', 'disconnect', 'gotMenus', 'menu'}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from sat_frontends.quick_frontend import quick_widgets
|
|||
from sat_frontends.quick_frontend import quick_menus
|
||||
from sat_frontends.quick_frontend import quick_blog
|
||||
from sat_frontends.quick_frontend import quick_chat, quick_games
|
||||
from sat_frontends.quick_frontend import quick_contact_list
|
||||
from sat_frontends.quick_frontend.constants import Const as C
|
||||
|
||||
import sys
|
||||
|
|
@ -95,8 +96,7 @@ class ProfileManager(object):
|
|||
|
||||
def _plug_profile_gotCachedValues(self, cached_values):
|
||||
# add the contact list and its listener
|
||||
contact_list = self.host.addContactList(self.profile)
|
||||
self.host.contact_lists[self.profile] = contact_list
|
||||
contact_list = self.host.contact_lists.addProfile(self.profile)
|
||||
|
||||
for entity, data in cached_values.iteritems():
|
||||
for key, value in data.iteritems():
|
||||
|
|
@ -192,8 +192,7 @@ class ProfilesManager(object):
|
|||
|
||||
# remove the contact list and its listener
|
||||
host = self._profiles[profile].host
|
||||
host.contact_lists[profile].onDelete()
|
||||
del host.contact_lists[profile]
|
||||
host.contact_lists[profile].unplug()
|
||||
|
||||
del self._profiles[profile]
|
||||
|
||||
|
|
@ -218,7 +217,7 @@ class QuickApp(object):
|
|||
self.profiles = ProfilesManager()
|
||||
self.ready_profiles = set() # profiles which are connected and ready
|
||||
self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready
|
||||
self.contact_lists = {}
|
||||
self.contact_lists = quick_contact_list.QuickContactListHandler(self)
|
||||
self.widgets = quick_widgets.QuickWidgetsManager(self)
|
||||
if check_options is not None:
|
||||
self.options = check_options()
|
||||
|
|
@ -296,7 +295,8 @@ class QuickApp(object):
|
|||
@property
|
||||
def alerts_count(self):
|
||||
"""Count the over whole alerts for all contact lists"""
|
||||
return sum([sum(clist._alerts.values()) for clist in self.contact_lists.values()])
|
||||
# FIXME
|
||||
# return sum([sum(clist._alerts.values()) for clist in self.contact_lists.values()])
|
||||
|
||||
def registerSignal(self, function_name, handler=None, iface="core", with_profile=True):
|
||||
"""Register a handler for a signal
|
||||
|
|
@ -448,14 +448,6 @@ class QuickApp(object):
|
|||
def clear_profile(self):
|
||||
self.profiles.clear()
|
||||
|
||||
def addContactList(self, profile):
|
||||
"""Method to subclass to add a contact list widget
|
||||
|
||||
will be called on each profile session build
|
||||
@return: a ContactList widget
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def newWidget(self, widget):
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -473,7 +465,7 @@ class QuickApp(object):
|
|||
def disconnectedHandler(self, profile):
|
||||
"""called when the connection is closed"""
|
||||
log.debug(_("Disconnected"))
|
||||
self.contact_lists[profile].clearContacts()
|
||||
self.contact_lists[profile].disconnect()
|
||||
self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile)
|
||||
|
||||
def actionNewHandler(self, action_data, id_, security_limit, profile):
|
||||
|
|
@ -481,8 +473,8 @@ class QuickApp(object):
|
|||
|
||||
def newContactHandler(self, jid_s, attributes, groups, profile):
|
||||
entity = jid.JID(jid_s)
|
||||
_groups = list(groups)
|
||||
self.contact_lists[profile].setContact(entity, _groups, attributes, in_roster=True)
|
||||
groups = list(groups)
|
||||
self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True)
|
||||
|
||||
def newMessageHandler(self, from_jid_s, msg, type_, to_jid_s, extra, profile):
|
||||
from_jid = jid.JID(from_jid_s)
|
||||
|
|
@ -516,7 +508,7 @@ class QuickApp(object):
|
|||
if isinstance(widget, quick_chat.QuickChat) and widget.manageMessage(from_jid, type_):
|
||||
visible = True
|
||||
break
|
||||
if visible:
|
||||
if visible: # FIXME: à virer gof:
|
||||
if self.isHidden(): # the window is hidden
|
||||
self.updateAlertsCounter(extra_inc=1)
|
||||
else:
|
||||
|
|
@ -730,7 +722,7 @@ class QuickApp(object):
|
|||
|
||||
def contactDeletedHandler(self, jid_s, profile):
|
||||
target = jid.JID(jid_s)
|
||||
self.contact_lists[profile].removeContact(target, in_roster=True)
|
||||
self.contact_lists[profile].removeContact(target)
|
||||
|
||||
def entityDataUpdatedHandler(self, entity_s, key, value, profile):
|
||||
entity = jid.JID(entity_s)
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@
|
|||
# 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/>.
|
||||
|
||||
"""Contact List handling multi profiles at once, should replace quick_contact_list module in the future"""
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat.core import exceptions
|
||||
from sat_frontends.quick_frontend.quick_widgets import QuickWidget
|
||||
from sat_frontends.quick_frontend.constants import Const as C
|
||||
from sat_frontends.tools import jid
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
try:
|
||||
|
|
@ -37,14 +41,19 @@ except (TypeError, AttributeError): # Error raised is not the same depending on
|
|||
iter_cpy.sort(key=key)
|
||||
return pyjamas_max(iter_cpy)
|
||||
|
||||
handler = None
|
||||
|
||||
class QuickContactList(QuickWidget):
|
||||
"""This class manage the visual representation of contacts"""
|
||||
|
||||
def __init__(self, host, profile):
|
||||
log.debug(_("Contact List init"))
|
||||
super(QuickContactList, self).__init__(host, profile, profile)
|
||||
class ProfileContactList(object):
|
||||
"""Contact list data for a single profile"""
|
||||
|
||||
def __init__(self, profile):
|
||||
self.host = handler.host
|
||||
self.profile = profile
|
||||
# contain all jids in roster or not,
|
||||
# bare jids as keys, resources are used in data
|
||||
# XXX: we don't mutualise cache, as values may differ
|
||||
# for different profiles (e.g. directed presence)
|
||||
self._cache = {}
|
||||
|
||||
# special entities (groupchat, gateways, etc), bare jids
|
||||
|
|
@ -53,38 +62,50 @@ class QuickContactList(QuickWidget):
|
|||
self._special_extras = set()
|
||||
|
||||
# group data contain jids in groups and misc frontend data
|
||||
# None key is used for jids with not group
|
||||
self._groups = {} # groups to group data map
|
||||
|
||||
# contacts in roster (bare jids)
|
||||
self._roster = set()
|
||||
|
||||
# entities with alert(s) and their counts (usually a waiting message), dict{full jid: int)
|
||||
self._alerts = dict()
|
||||
# alerts per entity (key: full jid, value: list of alerts)
|
||||
self._alerts = {}
|
||||
|
||||
# selected entities, full jid
|
||||
self._selected = set()
|
||||
|
||||
# we keep our own jid
|
||||
self.whoami = host.profiles[profile].whoami
|
||||
self.whoami = self.host.profiles[profile].whoami
|
||||
|
||||
# options
|
||||
self.show_disconnected = False
|
||||
self.show_empty_groups = True
|
||||
self.show_resources = False
|
||||
self.show_status = False
|
||||
# TODO: this may lead to two successive UI refresh and needs an optimization
|
||||
|
||||
self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS, "General", profile_key=profile, callback=self._showEmptyGroups)
|
||||
self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS, "General", profile_key=profile, callback=self._showOfflineContacts)
|
||||
|
||||
# FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
|
||||
self.presenceListener = self.onPresenceUpdate
|
||||
self.host.addListener('presence', self.presenceListener, [profile])
|
||||
self.host.addListener('presence', self.presenceListener, [self.profile])
|
||||
self.nickListener = self.onNickUpdate
|
||||
self.host.addListener('nick', self.nickListener, [profile])
|
||||
self.host.addListener('nick', self.nickListener, [self.profile])
|
||||
|
||||
def _showEmptyGroups(self, show_str):
|
||||
# Called only by __init__
|
||||
# self.update is not wanted here, as it is done by
|
||||
# handler when all profiles are ready
|
||||
self.showEmptyGroups(C.bool(show_str))
|
||||
|
||||
def _showOfflineContacts(self, show_str):
|
||||
# same comments as for _showEmptyGroups
|
||||
self.showOfflineContacts(C.bool(show_str))
|
||||
|
||||
def __contains__(self, entity):
|
||||
"""Check if entity is in contact list
|
||||
|
||||
An entity can be in contact list even if not in roster
|
||||
@param entity (jid.JID): jid of the entity (resource is not ignored, use bare jid if needed)
|
||||
"""
|
||||
if entity.resource:
|
||||
|
|
@ -95,61 +116,97 @@ class QuickContactList(QuickWidget):
|
|||
return entity in self._cache
|
||||
|
||||
@property
|
||||
def roster_entities(self):
|
||||
def roster(self):
|
||||
"""Return all the bare JIDs of the roster entities.
|
||||
|
||||
@return: set(jid.JID)
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
return self._roster
|
||||
|
||||
@property
|
||||
def roster_entities_connected(self):
|
||||
def roster_connected(self):
|
||||
"""Return all the bare JIDs of the roster entities that are connected.
|
||||
|
||||
@return: set(jid.JID)
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
return set([entity for entity in self._roster if self.getCache(entity, C.PRESENCE_SHOW) is not None])
|
||||
|
||||
@property
|
||||
def roster_entities_by_group(self):
|
||||
"""Return a dictionary binding the roster groups to their entities bare
|
||||
JIDs. This also includes the empty group (None key).
|
||||
"""Return a dictionary binding the roster groups to their entities bare JIDs.
|
||||
|
||||
@return: dict{unicode: set(jid.JID)}
|
||||
This also includes the empty group (None key).
|
||||
@return (dict[unicode,set(jid.JID)])
|
||||
"""
|
||||
return {group: self._groups[group]['jids'] for group in self._groups}
|
||||
|
||||
@property
|
||||
def roster_groups_by_entity(self):
|
||||
"""Return a dictionary binding the entities bare JIDs to their roster
|
||||
groups. The empty group is filtered out.
|
||||
def roster_groups_by_entities(self):
|
||||
"""Return a dictionary binding the entities bare JIDs to their roster groups
|
||||
|
||||
@return: dict{jid.JID: set(unicode)}
|
||||
@return (dict[jid.JID, set(unicode)])
|
||||
"""
|
||||
result = {}
|
||||
for group, data in self._groups.iteritems():
|
||||
if group is None:
|
||||
continue
|
||||
for entity in data['jids']:
|
||||
result.setdefault(entity, set()).add(group)
|
||||
return result
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
"""Return contacts currently selected
|
||||
|
||||
@return (set): set of selected entities
|
||||
"""
|
||||
return self._selected
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
"""Return item representation for all visible entities in cache
|
||||
|
||||
entities are not sorted
|
||||
key: bare jid, value: data
|
||||
"""
|
||||
return {jid_:cache for jid_, cache in self._cache.iteritems() if self.entityToShow(jid_)}
|
||||
|
||||
def getItem(self, entity):
|
||||
"""Return item representation of requested entity
|
||||
|
||||
@param entity(jid.JID): bare jid of entity
|
||||
@raise (KeyError): entity is unknown
|
||||
"""
|
||||
return self._cache[entity]
|
||||
|
||||
def getSpecialExtras(self, special_type=None):
|
||||
"""Return special extras with given type
|
||||
|
||||
If special_type is None, return all special extras.
|
||||
|
||||
@param special_type(unicode, None): one of special type (e.g. C.CONTACT_SPECIAL_GROUP)
|
||||
None to return all special extras.
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
if special_type is None:
|
||||
return self._special_extras
|
||||
specials = self.getSpecials(special_type)
|
||||
return {extra for extra in self._special_extras if extra.bare in specials}
|
||||
|
||||
def _gotContacts(self, contacts):
|
||||
"""Called during filling, add contacts and notice parent that contacts are filled"""
|
||||
for contact in contacts:
|
||||
self.host.newContactHandler(*contact, profile=self.profile)
|
||||
handler._contactsFilled(self.profile)
|
||||
|
||||
def fill(self):
|
||||
"""Get all contacts from backend, and fill the widget
|
||||
def _fill(self):
|
||||
"""Get all contacts from backend
|
||||
|
||||
Contacts will be cleared before refilling them
|
||||
"""
|
||||
self.clearContacts(keep_cache=True)
|
||||
|
||||
self.host.bridge.getContacts(self.profile, callback=self._gotContacts)
|
||||
|
||||
def update(self):
|
||||
"""Update the display when something changed"""
|
||||
raise NotImplementedError
|
||||
def fill(self):
|
||||
handler.fill(self.profile)
|
||||
|
||||
def getCache(self, entity, name=None):
|
||||
"""Return a cache value for a contact
|
||||
|
|
@ -192,7 +249,7 @@ class QuickContactList(QuickWidget):
|
|||
@param entity(JID): entity to update
|
||||
@param name(unicode): value to set or update
|
||||
"""
|
||||
self.setContact(entity, None, {name: value})
|
||||
self.setContact(entity, attributes={name: value})
|
||||
|
||||
def getFullJid(self, entity):
|
||||
"""Get full jid from a bare jid
|
||||
|
|
@ -202,7 +259,7 @@ class QuickContactList(QuickWidget):
|
|||
@raise ValueError: the entity is not bare
|
||||
"""
|
||||
if entity.resource:
|
||||
raise ValueError("getFullJid must be used with a bare jid")
|
||||
raise ValueError(u"getFullJid must be used with a bare jid")
|
||||
main_resource = self.getCache(entity, C.CONTACT_MAIN_RESOURCE)
|
||||
return jid.JID(u"{}/{}".format(entity, main_resource))
|
||||
|
||||
|
|
@ -213,7 +270,6 @@ class QuickContactList(QuickWidget):
|
|||
@param name: name of the data (can't be "jids")
|
||||
@param value: value to set
|
||||
"""
|
||||
# FIXME: this is never used, should it be removed?
|
||||
assert name is not 'jids'
|
||||
self._groups[group][name] = value
|
||||
|
||||
|
|
@ -238,9 +294,9 @@ class QuickContactList(QuickWidget):
|
|||
self.setCache(entity, C.CONTACT_SPECIAL, special_type)
|
||||
|
||||
def getSpecials(self, special_type=None):
|
||||
"""Return all the bare JIDs of the special roster entities of the type
|
||||
specified by special_type. If special_type is None, return all specials.
|
||||
"""Return all the bare JIDs of the special roster entities of with given type.
|
||||
|
||||
If special_type is None, return all specials.
|
||||
@param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) or None to return all specials.
|
||||
@return: set(jid.JID)
|
||||
"""
|
||||
|
|
@ -248,24 +304,17 @@ class QuickContactList(QuickWidget):
|
|||
return self._specials
|
||||
return set([entity for entity in self._specials if self.getCache(entity, C.CONTACT_SPECIAL) == special_type])
|
||||
|
||||
def getSpecialExtras(self, special_type=None):
|
||||
"""Return all the JIDs of the special extras entities that are related
|
||||
to a special entity of the type specified by special_type.
|
||||
If special_type is None, return all special extras.
|
||||
|
||||
@param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) or None to return all special extras.
|
||||
@return: set(jid.JID)
|
||||
"""
|
||||
if special_type is None:
|
||||
return self._special_extras
|
||||
return set([extra for extra in self._special_extras if extra.bare in self.getSpecials(special_type)])
|
||||
def disconnect(self):
|
||||
# for now we just clear contacts on disconnect
|
||||
self.clearContacts()
|
||||
|
||||
def clearContacts(self, keep_cache=False):
|
||||
"""Clear all the contact list
|
||||
|
||||
@param keep_cache: if True, don't reset the cache
|
||||
"""
|
||||
self.unselectAll()
|
||||
self.select(None)
|
||||
if not keep_cache:
|
||||
self._cache.clear()
|
||||
self._groups.clear()
|
||||
|
|
@ -295,11 +344,13 @@ class QuickContactList(QuickWidget):
|
|||
attributes = {}
|
||||
|
||||
entity_bare = entity.bare
|
||||
update_type = C.UPDATE_MODIFY if entity_bare in self._cache else C.UPDATE_ADD
|
||||
|
||||
if in_roster:
|
||||
self._roster.add(entity_bare)
|
||||
|
||||
cache = self._cache.setdefault(entity_bare, {C.CONTACT_RESOURCES: {}})
|
||||
cache = self._cache.setdefault(entity_bare, {C.CONTACT_RESOURCES: {},
|
||||
C.CONTACT_SELECTED: set()})
|
||||
|
||||
assert not C.CONTACT_DATA_FORBIDDEN.intersection(attributes) # we don't want forbidden data in attributes
|
||||
|
||||
|
|
@ -324,18 +375,12 @@ class QuickContactList(QuickWidget):
|
|||
self._specials.add(entity_bare)
|
||||
cache[C.CONTACT_MAIN_RESOURCE] = None
|
||||
|
||||
# now the attribute we keep in cache
|
||||
# now the attributes we keep in cache
|
||||
for attribute, value in attributes.iteritems():
|
||||
cache[attribute] = value
|
||||
|
||||
# we can update the display
|
||||
self.update()
|
||||
|
||||
def getContacts(self):
|
||||
"""Return contacts currently selected
|
||||
|
||||
@return (set): set of selected entities"""
|
||||
return self._selected
|
||||
self.update([entity], update_type, self.profile)
|
||||
|
||||
def entityToShow(self, entity, check_resource=False):
|
||||
"""Tell if the contact should be showed or hidden.
|
||||
|
|
@ -362,8 +407,9 @@ class QuickContactList(QuickWidget):
|
|||
|
||||
@param entities (list[jid.JID]): list of jids
|
||||
@param check_resources (bool): True if resources must be significant
|
||||
@return: bool
|
||||
@return (bool): True if a least one entity need to be shown
|
||||
"""
|
||||
# FIXME: looks inefficient, really needed?
|
||||
for entity in entities:
|
||||
if self.entityToShow(entity, check_resources):
|
||||
return True
|
||||
|
|
@ -378,24 +424,25 @@ class QuickContactList(QuickWidget):
|
|||
"""
|
||||
return entity in self.getGroupData(group, "jids")
|
||||
|
||||
def removeContact(self, entity, in_roster=False):
|
||||
def removeContact(self, entity):
|
||||
"""remove a contact from the list
|
||||
|
||||
@param entity(jid.JID): jid of the entity to remove (bare jid is used)
|
||||
@param in_roster (bool): True if contact is from roster
|
||||
"""
|
||||
entity_bare = entity.bare
|
||||
try:
|
||||
groups = self._cache[entity_bare].get(C.CONTACT_GROUPS, set())
|
||||
except KeyError:
|
||||
log.warning(_(u"Trying to delete an unknow entity [{}]").format(entity))
|
||||
if in_roster:
|
||||
log.error(_(u"Trying to delete an unknow entity [{}]").format(entity))
|
||||
try:
|
||||
self._roster.remove(entity_bare)
|
||||
except KeyError:
|
||||
pass
|
||||
del self._cache[entity_bare]
|
||||
for group in groups:
|
||||
self._groups[group]['jids'].remove(entity_bare)
|
||||
if not self._groups[group]['jids']:
|
||||
self._groups.pop(group)
|
||||
self._groups.pop(group) # FIXME: we use pop because of pyjamas: http://wiki.goffi.org/wiki/Issues_with_Pyjamas/en
|
||||
for iterable in (self._selected, self._alerts, self._specials, self._special_extras):
|
||||
to_remove = set()
|
||||
for set_entity in iterable:
|
||||
|
|
@ -406,7 +453,7 @@ class QuickContactList(QuickWidget):
|
|||
else: # XXX: self._alerts is a dict
|
||||
for item in to_remove:
|
||||
del iterable[item]
|
||||
self.update()
|
||||
self.update([entity], C.UPDATE_DELETE, self.profile)
|
||||
|
||||
def onPresenceUpdate(self, entity, show, priority, statuses, profile):
|
||||
"""Update entity's presence status
|
||||
|
|
@ -440,7 +487,7 @@ class QuickContactList(QuickWidget):
|
|||
if entity.bare not in self._specials:
|
||||
priority_resource = max(resources_data, key=lambda res: resources_data[res][C.PRESENCE_PRIORITY])
|
||||
cache[C.CONTACT_MAIN_RESOURCE] = priority_resource
|
||||
self.update()
|
||||
self.update([entity], C.UPDATE_MODIFY, self.profile)
|
||||
|
||||
def onNickUpdate(self, entity, new_nick, profile):
|
||||
"""Update entity's nick
|
||||
|
|
@ -449,47 +496,82 @@ class QuickContactList(QuickWidget):
|
|||
@param new_nick(unicode): new nick of the entity
|
||||
@param profile: %(doc_profile)s
|
||||
"""
|
||||
raise NotImplementedError # Must be implemented by frontends
|
||||
assert profile == self.profile
|
||||
self.setCache(entity, 'nick', new_nick)
|
||||
self.update([entity], C.UPDATE_MODIFY, profile)
|
||||
|
||||
def unselectAll(self):
|
||||
"""Unselect all contacts"""
|
||||
self._selected.clear()
|
||||
self.update()
|
||||
def unselect(self, entity):
|
||||
"""Unselect an entity
|
||||
|
||||
@param entity(jid.JID): entity to unselect
|
||||
"""
|
||||
try:
|
||||
cache = self._cache[entity.bare]
|
||||
except:
|
||||
log.error(u"Try to unselect an entity not in cache")
|
||||
else:
|
||||
try:
|
||||
cache[C.CONTACT_SELECTED].remove(entity.resource)
|
||||
except KeyError:
|
||||
log.error(u"Try to unselect a not selected entity")
|
||||
else:
|
||||
self._selected.remove(entity)
|
||||
self.update([entity], C.UPDATE_SELECTION)
|
||||
|
||||
def select(self, entity):
|
||||
"""Select an entity
|
||||
|
||||
@param entity(jid.JID): entity to select (resource is significant)
|
||||
@param entity(jid.JID, None): entity to select (resource is significant)
|
||||
None to unselect all entities
|
||||
"""
|
||||
log.debug(u"select %s" % entity)
|
||||
self._selected.add(entity)
|
||||
self.update()
|
||||
if entity is None:
|
||||
self._selected.clear()
|
||||
for cache in self._cache.itervalues():
|
||||
cache[C.CONTACT_SELECTED].clear()
|
||||
self.update(type_=C.UPDATE_SELECTION, profile=self.profile)
|
||||
else:
|
||||
log.debug(u"select %s" % entity)
|
||||
try:
|
||||
cache = self._cache[entity.bare]
|
||||
except:
|
||||
log.error(u"Try to select an entity not in cache")
|
||||
else:
|
||||
cache[C.CONTACT_SELECTED].add(entity.resource)
|
||||
self._selected.add(entity)
|
||||
self.update([entity], C.UPDATE_SELECTION, profile=self.profile)
|
||||
|
||||
def getAlerts(self, entity, use_bare_jid=False, filter_=None):
|
||||
"""Return alerts set to this entity.
|
||||
|
||||
def getAlerts(self, entity, use_bare_jid=False):
|
||||
"""Return the number of alerts set to this entity.
|
||||
|
||||
@param entity (jid.JID): entity
|
||||
@param use_bare_jid (bool): if True, cumulate the alerts of all the resources sharing the same bare JID
|
||||
@return int
|
||||
@param filter_(iterable, None): alert to take into account,
|
||||
None to count all of them
|
||||
@return (list[unicode,None]): list of C.ALERT_* or None for undefined ones
|
||||
"""
|
||||
if not use_bare_jid:
|
||||
return self._alerts.get(entity, 0)
|
||||
|
||||
alerts = {}
|
||||
for contact in self._alerts:
|
||||
alerts.setdefault(contact.bare, 0)
|
||||
alerts[contact.bare] += self._alerts[contact]
|
||||
return alerts.get(entity.bare, 0)
|
||||
alerts = self._alerts.get(entity, [])
|
||||
else:
|
||||
alerts = []
|
||||
for contact, contact_alerts in self._alerts:
|
||||
if contact.bare == entity:
|
||||
alerts.extend(contact_alerts)
|
||||
if filter_ is None:
|
||||
return alerts
|
||||
else:
|
||||
return [alert for alert in alerts if alert in filter_]
|
||||
|
||||
def addAlert(self, entity):
|
||||
"""Increase the alerts counter for this entity (usually for a waiting message)
|
||||
def addAlert(self, entity, type_=None):
|
||||
"""Add an alert for this enity
|
||||
|
||||
@param entity(jid.JID): entity which must displayed in alert mode (resource is significant)
|
||||
@param entity(jid.JID): entity who received an alert (resource is significant)
|
||||
@param type_(unicode, None): type of alert (C.ALERT_*)
|
||||
None for generic alert
|
||||
"""
|
||||
self._alerts.setdefault(entity, 0)
|
||||
self._alerts[entity] += 1
|
||||
self.update()
|
||||
self.host.updateAlertsCounter()
|
||||
self._alerts.setdefault(entity, [])
|
||||
self._alerts[entity].append(type_)
|
||||
self.update([entity], C.UPDATE_MODIFY, self.profile)
|
||||
self.host.updateAlertsCounter() # FIXME: ?
|
||||
|
||||
def removeAlerts(self, entity, use_bare_jid=True):
|
||||
"""Eventually remove an alert on the entity (usually for a waiting message).
|
||||
|
|
@ -506,16 +588,16 @@ class QuickContactList(QuickWidget):
|
|||
return # nothing changed
|
||||
for entity in to_remove:
|
||||
del self._alerts[entity]
|
||||
self.update([to_remove], C.UPDATE_MODIFY, self.profile)
|
||||
self.host.updateAlertsCounter() # FIXME: ?
|
||||
else:
|
||||
try:
|
||||
del self._alerts[entity]
|
||||
except KeyError:
|
||||
return # nothing changed
|
||||
self.update()
|
||||
self.host.updateAlertsCounter()
|
||||
|
||||
def _showOfflineContacts(self, show_str):
|
||||
self.showOfflineContacts(C.bool(show_str))
|
||||
else:
|
||||
self.update([entity], C.UPDATE_MODIFY, self.profile)
|
||||
self.host.updateAlertsCounter() # FIXME: ?
|
||||
|
||||
def showOfflineContacts(self, show):
|
||||
"""Tell if offline contacts should shown
|
||||
|
|
@ -526,25 +608,302 @@ class QuickContactList(QuickWidget):
|
|||
if self.show_disconnected == show:
|
||||
return
|
||||
self.show_disconnected = show
|
||||
self.update()
|
||||
|
||||
def _showEmptyGroups(self, show_str):
|
||||
self.showEmptyGroups(C.bool(show_str))
|
||||
self.update(type_=C.UPDATE_STRUCTURE, profile=self.profile)
|
||||
|
||||
def showEmptyGroups(self, show):
|
||||
assert isinstance(show, bool)
|
||||
if self.show_empty_groups == show:
|
||||
return
|
||||
self.show_empty_groups = show
|
||||
self.update()
|
||||
self.update(type_=C.UPDATE_STRUCTURE, profile=self.profile)
|
||||
|
||||
def showResources(self, show):
|
||||
assert isinstance(show, bool)
|
||||
if self.show_resources == show:
|
||||
return
|
||||
self.show_resources = show
|
||||
self.update(profile=self.profile)
|
||||
|
||||
def plug(self):
|
||||
handler.addProfile(self.profile)
|
||||
|
||||
def unplug(self):
|
||||
handler.removeProfile(self.profile)
|
||||
|
||||
def update(self, entities=None, type_=None, profile=None):
|
||||
handler.update(entities, type_, profile)
|
||||
|
||||
|
||||
class QuickContactListHandler(object):
|
||||
|
||||
def __init__(self, host):
|
||||
super(QuickContactListHandler, self).__init__()
|
||||
self.host = host
|
||||
global handler
|
||||
if handler is not None:
|
||||
raise exceptions.InternalError(u"QuickContactListHandler must be instanciated only once")
|
||||
handler = self
|
||||
self._clist = {} # key: profile, value: ProfileContactList
|
||||
self._widgets = set()
|
||||
|
||||
def __getitem__(self, profile):
|
||||
"""Return ProfileContactList instance for the requested profile"""
|
||||
return self._clist[profile]
|
||||
|
||||
def __contains__(self, entity):
|
||||
"""Check if entity is in contact list
|
||||
|
||||
@param entity (jid.JID): jid of the entity (resource is not ignored, use bare jid if needed)
|
||||
"""
|
||||
for contact_list in self._clist.itervalues():
|
||||
if entity in contact_list:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def roster_entities(self):
|
||||
"""Return all the bare JIDs of the roster entities.
|
||||
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
entities = set()
|
||||
for contact_list in self._clist.itervalues():
|
||||
entities.update(contact_list.roster_entities)
|
||||
return entities
|
||||
|
||||
@property
|
||||
def roster_entities_connected(self):
|
||||
"""Return all the bare JIDs of the roster entities that are connected.
|
||||
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
entities = set()
|
||||
for contact_list in self._clist.itervalues():
|
||||
entities.update(contact_list.roster_entities_connected)
|
||||
return entities
|
||||
|
||||
@property
|
||||
def roster_entities_by_group(self):
|
||||
"""Return a dictionary binding the roster groups to their entities bare
|
||||
JIDs. This also includes the empty group (None key).
|
||||
|
||||
@return (dict[unicode,set(jid.JID)])
|
||||
"""
|
||||
groups = {}
|
||||
for contact_list in self._clist.itervalues():
|
||||
groups.update(contact_list.roster_entities_by_group)
|
||||
return groups
|
||||
|
||||
@property
|
||||
def roster_groups_by_entities(self):
|
||||
"""Return a dictionary binding the entities bare JIDs to their roster
|
||||
groups.
|
||||
|
||||
@return (dict[jid.JID, set(unicode)])
|
||||
"""
|
||||
entities = {}
|
||||
for contact_list in self._clist.itervalues():
|
||||
entities.update(contact_list.roster_groups_by_entities)
|
||||
return entities
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
"""Return contacts currently selected
|
||||
|
||||
@return (set): set of selected entities
|
||||
"""
|
||||
entities = set()
|
||||
for contact_list in self._clist.itervalues():
|
||||
entities.update(contact_list.selected)
|
||||
return entities
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
"""Return item representation for all visible entities in cache
|
||||
|
||||
items are unordered
|
||||
key: bare jid, value: data
|
||||
"""
|
||||
items = {}
|
||||
for profile, contact_list in self._clist.iteritems():
|
||||
for bare_jid, cache in contact_list.items.iteritems():
|
||||
data = cache.copy()
|
||||
items[bare_jid] = data
|
||||
data[C.CONTACT_PROFILE] = profile
|
||||
items.update(contact_list.items)
|
||||
return items
|
||||
|
||||
@property
|
||||
def items_sorted(self):
|
||||
"""Return item representation for all visible entities in cache
|
||||
|
||||
items are ordered using self.items_sort
|
||||
key: bare jid, value: data
|
||||
"""
|
||||
return self.items_sort(self.items)
|
||||
|
||||
def items_sort(self, items):
|
||||
"""sort items
|
||||
|
||||
@param items(dict): items to sort (we be emptied !)
|
||||
@return (OrderedDict): sorted items
|
||||
"""
|
||||
ordered_items = OrderedDict()
|
||||
bare_jids = sorted(items.keys())
|
||||
for jid_ in bare_jids:
|
||||
ordered_items[jid_] = items.pop(jid_)
|
||||
return ordered_items
|
||||
|
||||
def register(self, widget):
|
||||
"""Register a QuickContactList widget
|
||||
|
||||
This method should only be used in QuickContactList
|
||||
"""
|
||||
self._widgets.add(widget)
|
||||
|
||||
def unregister(self, widget):
|
||||
"""Unregister a QuickContactList widget
|
||||
|
||||
This method should only be used in QuickContactList
|
||||
"""
|
||||
self._widgets.remove(widget)
|
||||
|
||||
def addProfiles(self, profiles):
|
||||
"""Add a contact list for plugged profiles
|
||||
|
||||
@param profile(iterable[unicode]): plugged profiles
|
||||
"""
|
||||
for profile in profiles:
|
||||
if profile not in self._clist:
|
||||
self._clist[profile] = ProfileContactList(profile)
|
||||
return [self._clist[profile] for profile in profiles]
|
||||
|
||||
def addProfile(self, profile):
|
||||
return self.addProfiles([profile])[0]
|
||||
|
||||
def removeProfiles(self, profiles):
|
||||
"""Remove given unplugged profiles from contact list
|
||||
|
||||
@param profile(iterable[unicode]): unplugged profiles
|
||||
"""
|
||||
for profile in profiles:
|
||||
del self._clist[profile]
|
||||
|
||||
def removeProfile(self, profile):
|
||||
self.removeProfiles([profile])
|
||||
|
||||
def getSpecialExtras(self, special_type=None):
|
||||
"""Return special extras with given type
|
||||
|
||||
If special_type is None, return all special extras.
|
||||
|
||||
@param special_type(unicode, None): one of special type (e.g. C.CONTACT_SPECIAL_GROUP)
|
||||
None to return all special extras.
|
||||
@return (set[jid.JID])
|
||||
"""
|
||||
entities = set()
|
||||
for contact_list in self._clist.itervalues():
|
||||
entities.update(contact_list.getSpecialExtras(special_type))
|
||||
return entities
|
||||
|
||||
def _contactsFilled(self, profile):
|
||||
self._to_fill.remove(profile)
|
||||
if not self._to_fill:
|
||||
del self._to_fill
|
||||
self.update()
|
||||
|
||||
def fill(self, profile=None):
|
||||
"""Get all contacts from backend, and fill the widget
|
||||
|
||||
Contacts will be cleared before refilling them
|
||||
@param profile(unicode, None): profile to fill
|
||||
None to fill all profiles
|
||||
"""
|
||||
try:
|
||||
to_fill = self._to_fill
|
||||
except AttributeError:
|
||||
to_fill = self._to_fill = set()
|
||||
|
||||
# if check if profiles have already been filled
|
||||
# to void filling them several times
|
||||
filled = to_fill.copy()
|
||||
|
||||
if profile is not None:
|
||||
assert profile in self._clist
|
||||
to_fill.add(profile)
|
||||
else:
|
||||
to_fill.update(self._clist.items())
|
||||
|
||||
remaining = to_fill.difference(filled)
|
||||
if remaining != to_fill:
|
||||
log.debug(u"Not re-filling already filled contact list(s) for {}".format(u', '.join(to_fill.intersection(filled))))
|
||||
for profile in remaining:
|
||||
self._clist[profile]._fill()
|
||||
|
||||
def clearContacts(self, keep_cache=False):
|
||||
"""Clear all the contact list
|
||||
|
||||
@param keep_cache: if True, don't reset the cache
|
||||
"""
|
||||
for contact_list in self._clist.itervalues():
|
||||
contact_list.clearContacts(keep_cache)
|
||||
self.update()
|
||||
|
||||
def select(self, entity):
|
||||
for contact_list in self._clist.itervalues():
|
||||
contact_list.select(entity)
|
||||
|
||||
def unselect(self, entity):
|
||||
for contact_list in self._clist.itervalues():
|
||||
contact_list.select(entity)
|
||||
|
||||
def update(self, entities=None, type_=None, profile=None):
|
||||
for widget in self._widgets:
|
||||
widget.update(entities, type_, profile)
|
||||
|
||||
|
||||
class QuickContactList(QuickWidget):
|
||||
"""This class manage the visual representation of contacts"""
|
||||
SINGLE=False
|
||||
PROFILES_MULTIPLE=True
|
||||
PROFILES_ALLOW_NONE=True # Can be linked to no profile (e.g. at the early forntend start)
|
||||
|
||||
def __init__(self, host, profiles):
|
||||
super(QuickContactList, self).__init__(host, None, profiles)
|
||||
handler.register(self)
|
||||
|
||||
# options
|
||||
# for next values, None means use indivual value per profile
|
||||
# True or False mean override these values for all profiles
|
||||
self.show_disconnected = None # TODO
|
||||
self.show_empty_groups = None # TODO
|
||||
self.show_resources = None # TODO
|
||||
self.show_status = None # TODO
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
return handler.items
|
||||
|
||||
@property
|
||||
def items_sorted(self):
|
||||
return handler.items
|
||||
|
||||
def update(self, entities=None, type_=None, profile=None):
|
||||
"""Update the display when something changed
|
||||
|
||||
@param entities(iterable[jid.JID], None): updated entities,
|
||||
None to update the whole contact list
|
||||
@param type_(unicode, None): update type, may be:
|
||||
- C.UPDATE_DELETE: entity deleted
|
||||
- C.UPDATE_MODIFY: entity updated
|
||||
- C.UPDATE_ADD: entity added
|
||||
- C.UPDATE_SELECTION: selection modified
|
||||
or None for undefined update
|
||||
@param profile(unicode, None): profile concerned with the update
|
||||
None if unknown
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def onDelete(self):
|
||||
QuickWidget.onDelete(self)
|
||||
self.host.removeListener('presence', self.presenceListener)
|
||||
handler.unregister(self)
|
||||
|
|
|
|||
|
|
@ -93,16 +93,20 @@ class QuickWidgetsManager(object):
|
|||
else:
|
||||
return widgets_map.itervalues()
|
||||
|
||||
def getWidget(self, class_, target, profile):
|
||||
def getWidget(self, class_, target=None, profiles=None):
|
||||
"""Get a widget without creating it if it doesn't exist.
|
||||
|
||||
@param class_(class): class of the widget to create
|
||||
@param target: target depending of the widget, usually a JID instance
|
||||
@param profile (unicode): %(doc_profile)s
|
||||
@param profiles (unicode, iterable[unicode], None): profile(s) to use (may or may not be
|
||||
used, depending of the widget class)
|
||||
@return: a class_ instance or None if the widget doesn't exist
|
||||
"""
|
||||
assert (target is not None) or (profiles is not None)
|
||||
if profiles is not None and isinstance(profiles, unicode):
|
||||
profiles = [profiles]
|
||||
class_ = self.getRealClass(class_)
|
||||
hash_ = class_.getWidgetHash(target, profile)
|
||||
hash_ = class_.getWidgetHash(target, profiles)
|
||||
try:
|
||||
return self._widgets[class_.__name__][hash_]
|
||||
except KeyError:
|
||||
|
|
@ -140,7 +144,7 @@ class QuickWidgetsManager(object):
|
|||
if 'profiles' in _kwargs and 'profile' in _kwargs:
|
||||
raise ValueError("You can't have 'profile' and 'profiles' keys at the same time")
|
||||
try:
|
||||
_kwargs['profiles'] = _kwargs.pop('profile')
|
||||
_kwargs['profiles'] = [_kwargs.pop('profile')]
|
||||
except KeyError:
|
||||
if not 'profiles' in _kwargs:
|
||||
_kwargs['profiles'] = None
|
||||
|
|
@ -266,7 +270,7 @@ class QuickWidget(object):
|
|||
if not self.PROFILES_ALLOW_NONE:
|
||||
raise ValueError("profiles can't have a value of None")
|
||||
else:
|
||||
if not self.PROFILES_MULTIPLE:
|
||||
if not self.PROFILES_MULTIPLE and len(profiles) != 1:
|
||||
raise ValueError("multiple profiles are not allowed")
|
||||
for profile in profiles:
|
||||
self.addProfile(profile)
|
||||
|
|
|
|||
Loading…
Reference in a new issue