From 61f1d1de6ae93db95c4affd7e9da22dc7790b5fd Mon Sep 17 00:00:00 2001 From: souliane Date: Tue, 10 Feb 2015 10:33:54 +0100 Subject: [PATCH 01/11] quick_frontend (quick_widgets): revert commit 1319 --- frontends/src/quick_frontend/quick_widgets.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/frontends/src/quick_frontend/quick_widgets.py b/frontends/src/quick_frontend/quick_widgets.py index aac9f09b..0cbc4a4d 100644 --- a/frontends/src/quick_frontend/quick_widgets.py +++ b/frontends/src/quick_frontend/quick_widgets.py @@ -105,15 +105,13 @@ class QuickWidgetsManager(object): if there is neither 'profile' nor 'profiles', None will be used for 'profiles' if 'on_new_widget' is present it can have the following values: C.WIDGET_NEW [default]: self.host.newWidget will be called on widget creation - [callable]: this method will be called instead of self.host.newWidget, - and can return another widget to modify the result of the present method + [callable]: this method will be called instead of self.host.newWidget None: do nothing if 'on_existing_widget' is present it can have the following values: C.WIDGET_KEEP [default]: return the existing widget C.WIDGET_RAISE: raise WidgetAlreadyExistsError C.WIDGET_RECREATE: create a new widget *WITH A NEW HASH* - [callable]: this method will be called with existing widget as argument, - and can return another widget to modify the result of the present method + [callable]: this method will be called with existing widget as argument if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.getWidgetHash other keys will be used to instanciate class_ if the case happen (e.g. if type_ is present and class_ is a QuickChat subclass, it will be used to create a new QuickChat instance). @@ -171,9 +169,7 @@ class QuickWidgetsManager(object): if on_new_widget == C.WIDGET_NEW: self.host.newWidget(widget) elif callable(on_new_widget): - result = on_new_widget(widget) - if isinstance(result, QuickWidget): - widget = result + on_new_widget(widget) else: assert on_new_widget is None else: @@ -207,9 +203,7 @@ class QuickWidgetsManager(object): log.debug(u"Widget already exists, a new one has been recreated with hash {}".format(new_kwargs['force_hash'])) break elif callable(on_existing_widget): - result = on_existing_widget(widget) - if isinstance(result, QuickWidget): - widget = result + on_existing_widget(widget) else: raise exceptions.InternalError("Unexpected on_existing_widget value ({})".format(on_existing_widget)) From 269e2a7c8b17ee23276fe3a9d61e68e8cd3acace Mon Sep 17 00:00:00 2001 From: souliane Date: Fri, 20 Feb 2015 22:06:13 +0100 Subject: [PATCH 02/11] frontends (quick_frontend): fixes a typo (listerner --> listener) --- frontends/src/quick_frontend/quick_app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index cd077a30..3775c405 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -196,7 +196,7 @@ class QuickApp(object): self.selected_widget = None # widget currently selected (must be filled by frontend) # listeners - self._listeners = {} # key: listerner type ("avatar", "selected", etc), value: list of callbacks + self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks ## bridge ## try: @@ -287,8 +287,8 @@ class QuickApp(object): handler(*args, **kwargs) self.bridge.register(functionName, signalReceived, iface) - def addListerner(self, type_, callback): - """Add a listerner for an event + def addListener(self, type_, callback): + """Add a listener for an event /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget) @param type_: type of event, can be: @@ -302,7 +302,7 @@ class QuickApp(object): def removeListener(self, type_, callback): """Remove a callback from listeners - @param type_: same as for [addListerner] + @param type_: same as for [addListener] @param callback: callback to remove """ assert type_ in C.LISTENERS @@ -311,7 +311,7 @@ class QuickApp(object): def callListeners(self, type_, *args): """Call all methods which listen of type_ event - @param type_: same as for [addListerner] + @param type_: same as for [addListener] @param *args: arguments sent to callback """ assert type_ in C.LISTENERS From 4c7e81ad6c16ac169c0eee9978a02b51404417c3 Mon Sep 17 00:00:00 2001 From: souliane Date: Sat, 21 Feb 2015 12:34:48 +0100 Subject: [PATCH 03/11] quick_frontend, primitivus: fixes typo between method alert and class Alert --- frontends/src/primitivus/profile_manager.py | 2 +- frontends/src/quick_frontend/quick_profile_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontends/src/primitivus/profile_manager.py b/frontends/src/primitivus/profile_manager.py index 0b1d087a..148f2cc8 100644 --- a/frontends/src/primitivus/profile_manager.py +++ b/frontends/src/primitivus/profile_manager.py @@ -153,7 +153,7 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap): self.host.redraw() def alert(self, title, message): - popup = sat_widgets.alert(title, message, ok_cb=self.host.removePopUp) + popup = sat_widgets.Alert(title, message, ok_cb=self.host.removePopUp) self.host.showPopUp(popup) def onProfileChange(self, list_wid): diff --git a/frontends/src/quick_frontend/quick_profile_manager.py b/frontends/src/quick_frontend/quick_profile_manager.py index 8e16a24b..58832069 100644 --- a/frontends/src/quick_frontend/quick_profile_manager.py +++ b/frontends/src/quick_frontend/quick_profile_manager.py @@ -113,7 +113,7 @@ class QuickProfileManager(object): self._autoconnect = False # manual mode msg = _("Trying to plug an unknown profile key ({})".format(profile_key)) log.warning(msg) - self.alert(_("Profile plugging in error"), msg, ok_cb=self.host.removePopUp) + self.alert(_("Profile plugging in error"), msg) break self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile) From b5d70cbe3473c916d18b6385ba7342b261b4d156 Mon Sep 17 00:00:00 2001 From: souliane Date: Sat, 21 Feb 2015 17:22:39 +0100 Subject: [PATCH 04/11] core, quick_frontends: fixes one typo and a variable naming issue --- frontends/src/quick_frontend/quick_app.py | 6 +++--- src/core/sat_main.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index 3775c405..bc56a27f 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -619,7 +619,7 @@ class QuickApp(object): # this is a subscriptionn request, we have to ask for user confirmation self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.bare, _('Subscription confirmation'), 'yes/no', answer_cb=self._subscribe_cb, answer_data=(entity, profile)) - def showDialog(self, message, title, type="info", answer_cb=None): + def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): raise NotImplementedError def showAlert(self, message): @@ -637,8 +637,8 @@ class QuickApp(object): elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS): self.contact_lists[profile].showEmptyGroups(C.bool(value)) - def contactDeletedHandler(self, jid, profile): - target = jid.JID(jid) + def contactDeletedHandler(self, jid_s, profile): + target = jid.JID(jid_s) self.contact_lists[profile].remove(target) def entityDataUpdatedHandler(self, entity_s, key, value, profile): diff --git a/src/core/sat_main.py b/src/core/sat_main.py index fd315b1d..d8ac3e87 100644 --- a/src/core/sat_main.py +++ b/src/core/sat_main.py @@ -640,7 +640,7 @@ class SAT(service.Service): assert profile d1 = self.profiles[profile].roster.removeItem(to_jid) d2 = self.profiles[profile].presence.unsubscribe(to_jid) - d_list = defer.DefferedList([d1, d2]) + d_list = defer.DeferredList([d1, d2]) def check_result(list_result): for success, value in list_result: if not success: From fa8f119a56c430cf341e6d481ae85b0b563ebd59 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 13:58:33 +0100 Subject: [PATCH 05/11] quick_frontend (quick_app): fixes the call to a bridge method that was done with a JID instead of unicode --- frontends/src/quick_frontend/quick_app.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index bc56a27f..d7d7c2c4 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -29,6 +29,13 @@ from sat_frontends.quick_frontend import quick_chat from sat_frontends.quick_frontend.constants import Const as C +try: + # FIXME: to be removed when an acceptable solution is here + unicode('') # XXX: unicode doesn't exist in pyjamas +except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode = str + + class ProfileManager(object): """Class managing all data relative to one profile, and plugging in mechanism""" host = None @@ -601,10 +608,8 @@ class QuickApp(object): def _subscribe_cb(self, answer, data): entity, profile = data - if answer: - self.bridge.subscription("subscribed", entity.bare, profile_key=profile) - else: - self.bridge.subscription("unsubscribed", entity.bare, profile_key=profile) + type_ = "subscribed" if answer else "unsubscribed" + self.bridge.subscription(type_, unicode(entity.bare), profile_key=profile) def subscribeHandler(self, type, raw_jid, profile): """Called when a subsciption management signal is received""" From 9461ac75ce18f63982757fb1c2e73fd7421f6195 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 14:13:42 +0100 Subject: [PATCH 06/11] frontends: merge sat_frontends.constants to sat_frontends.quick_frontend.constants --- frontends/src/constants.py | 75 ----------------------- frontends/src/primitivus/status.py | 2 +- frontends/src/quick_frontend/constants.py | 50 +++++++++++++-- frontends/src/tools/xmlui.py | 2 +- 4 files changed, 47 insertions(+), 82 deletions(-) delete mode 100644 frontends/src/constants.py diff --git a/frontends/src/constants.py b/frontends/src/constants.py deleted file mode 100644 index e3cd3dfb..00000000 --- a/frontends/src/constants.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# generic module for SàT frontends -# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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 . - - -from sat.core import constants -from sat.core.i18n import _, D_ -from collections import OrderedDict # only available from python 2.7 - - -def getPresence(): - """We cannot do it directly in the Const class, if it is not encapsulated - in a method we get a JS runtime SyntaxError: "missing ) in parenthetical". - # TODO: merge this definition with those in primitivus.constants - """ - - -class Const(constants.Const): - - PRESENCE = OrderedDict([("", _("Online")), - ("chat", _("Free for chat")), - ("away", _("Away from keyboard")), - ("dnd", _("Do not disturb")), - ("xa", _("Extended away"))]) - - # from plugin_misc_text_syntaxes - SYNTAX_XHTML = "XHTML" - SYNTAX_CURRENT = "@CURRENT@" - SYNTAX_TEXT = "text" - - # XMLUI - SAT_FORM_PREFIX = "SAT_FORM_" - SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names - XMLUI_STATUS_VALIDATED = "validated" - XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED - - # MUC - ALL_OCCUPANTS = 1 - MUC_USER_STATES = { - "active": u'✔', - "inactive": u'☄', - "gone": u'✈', - "composing": u'✎', - "paused": u"⦷" - } - - # Roster - GROUP_NOT_IN_ROSTER = D_('Not in roster') - - # Chats - CHAT_ONE2ONE = 'one2one' - CHAT_GROUP = 'group' - - # Widgets management - # FIXME: should be in quick_frontend.constant, but Libervia doesn't inherit from it - WIDGET_NEW = 'NEW' - WIDGET_KEEP = 'KEEP' - WIDGET_RAISE = 'RAISE' - WIDGET_RECREATE = 'RECREATE' - diff --git a/frontends/src/primitivus/status.py b/frontends/src/primitivus/status.py index 00123cba..91158d45 100644 --- a/frontends/src/primitivus/status.py +++ b/frontends/src/primitivus/status.py @@ -20,7 +20,7 @@ from sat.core.i18n import _ import urwid from urwid_satext import sat_widgets -from sat_frontends.constants import Const as commonConst +from sat_frontends.quick_frontend.constants import Const as commonConst from sat_frontends.primitivus.constants import Const diff --git a/frontends/src/quick_frontend/constants.py b/frontends/src/quick_frontend/constants.py index 0cb1a26d..0063e42c 100644 --- a/frontends/src/quick_frontend/constants.py +++ b/frontends/src/quick_frontend/constants.py @@ -17,18 +17,58 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from sat_frontends import constants +from sat.core import constants +from sat.core.i18n import _, D_ +from collections import OrderedDict # only available from python 2.7 class Const(constants.Const): - #Contact list + PRESENCE = OrderedDict([("", _("Online")), + ("chat", _("Free for chat")), + ("away", _("Away from keyboard")), + ("dnd", _("Do not disturb")), + ("xa", _("Extended away"))]) + + # from plugin_misc_text_syntaxes + SYNTAX_XHTML = "XHTML" + SYNTAX_CURRENT = "@CURRENT@" + SYNTAX_TEXT = "text" + + # XMLUI + SAT_FORM_PREFIX = "SAT_FORM_" + SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names + XMLUI_STATUS_VALIDATED = "validated" + XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED + + # MUC + ALL_OCCUPANTS = 1 + MUC_USER_STATES = { + "active": u'✔', + "inactive": u'☄', + "gone": u'✈', + "composing": u'✎', + "paused": u"⦷" + } + + # Roster CONTACT_GROUPS = 'groups' CONTACT_RESOURCES = 'resources' 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_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 + + # Chats + CHAT_ONE2ONE = 'one2one' + CHAT_GROUP = 'group' + + # Widgets management + # FIXME: should be in quick_frontend.constant, but Libervia doesn't inherit from it + WIDGET_NEW = 'NEW' + WIDGET_KEEP = 'KEEP' + WIDGET_RAISE = 'RAISE' + WIDGET_RECREATE = 'RECREATE' LISTENERS = {'avatar'} diff --git a/frontends/src/tools/xmlui.py b/frontends/src/tools/xmlui.py index 2573f25d..dc483f68 100644 --- a/frontends/src/tools/xmlui.py +++ b/frontends/src/tools/xmlui.py @@ -20,7 +20,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger log = getLogger(__name__) -from sat_frontends.constants import Const as C +from sat_frontends.quick_frontend.constants import Const as C from sat.core.exceptions import DataError From cf58422fcc135d1e2af31b85a051ad1f100860bd Mon Sep 17 00:00:00 2001 From: souliane Date: Sat, 21 Feb 2015 12:39:04 +0100 Subject: [PATCH 07/11] quick_frontend (quick_contact_list): add property roster_entities_connected and method getSpecials --- .../src/quick_frontend/quick_contact_list.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/frontends/src/quick_frontend/quick_contact_list.py b/frontends/src/quick_frontend/quick_contact_list.py index 82009c3d..f75bc3bf 100644 --- a/frontends/src/quick_frontend/quick_contact_list.py +++ b/frontends/src/quick_frontend/quick_contact_list.py @@ -91,17 +91,27 @@ class QuickContactList(QuickWidget): """ return self._roster + @property + def roster_entities_connected(self): + """Return all the bare JIDs of the roster entities that are connected. + + @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. + """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)} """ return {group: self._groups[group]['jids'] for group in self._groups} @property - def roster_groups_by_entity(self, contact_jid_s): - """Return a dictionary binding the entities bare JIDs to their roster groups. + def roster_groups_by_entity(self): + """Return a dictionary binding the entities bare JIDs to their roster + groups. The empty group is filtered out. @return: dict{jid.JID: set(unicode)} """ @@ -197,6 +207,17 @@ class QuickContactList(QuickWidget): assert special_type in C.CONTACT_SPECIAL_ALLOWED + (None,) 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. + + @param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) or None to return all specials. + @return: set(jid.JID) + """ + if special_type is None: + return self._specials + return set([entity for entity in self._specials if self.getCache(entity, C.CONTACT_SPECIAL) == special_type]) + def clearContacts(self): """Clear all the contact list""" self.unselectAll() From ed8821d54be499c0c4b5555f1e837817634a07d7 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 15:11:29 +0100 Subject: [PATCH 08/11] quick_frontend: add a 'presence' listener for the 'presenceUpdate' signal --- frontends/src/quick_frontend/constants.py | 2 +- frontends/src/quick_frontend/quick_app.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/src/quick_frontend/constants.py b/frontends/src/quick_frontend/constants.py index 0063e42c..7cb6e4b1 100644 --- a/frontends/src/quick_frontend/constants.py +++ b/frontends/src/quick_frontend/constants.py @@ -71,4 +71,4 @@ class Const(constants.Const): WIDGET_RAISE = 'RAISE' WIDGET_RECREATE = 'RECREATE' - LISTENERS = {'avatar'} + LISTENERS = {'avatar', 'presence'} diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index d7d7c2c4..a9c180d5 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -463,6 +463,7 @@ class QuickApp(object): # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare) self.contact_lists[profile].updatePresence(entity, show, priority, statuses) + self.callListeners('presence', entity, show, priority, statuses, profile) def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile): """Called when a MUC room is joined""" From d70f08d524ad9b5941ebdc89f958aec2f68be700 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 11:43:11 +0100 Subject: [PATCH 09/11] plugin XEP-0085: fixes the calls to memory.deleteEntityDatum and self.updateCache (was self.updateEntityData, has been renamed to avoid confusion with the memory method) --- src/plugins/plugin_xep_0085.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/plugins/plugin_xep_0085.py b/src/plugins/plugin_xep_0085.py index 8017c9d1..e2625a46 100755 --- a/src/plugins/plugin_xep_0085.py +++ b/src/plugins/plugin_xep_0085.py @@ -126,16 +126,16 @@ class XEP_0085(object): self.map[profile][to_jid]._onEvent('gone') del self.map[profile] - def updateEntityData(self, entity_jid, value, profile): + def updateCache(self, entity_jid, value, profile): """Update the entity data of the given profile for one or all contacts. - Reset the chat state(s) display if the notification has been disabled. + @param entity_jid: contact's JID, or C.ENTITY_ALL to update all contacts. @param value: True, False or DELETE_VALUE to delete the entity data @param profile: current profile """ if value == DELETE_VALUE: - self.host.memory.delEntityData(entity_jid, ENTITY_KEY, profile) + self.host.memory.delEntityDatum(entity_jid, ENTITY_KEY, profile) else: self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile_key=profile) if not value or value == DELETE_VALUE: @@ -151,7 +151,7 @@ class XEP_0085(object): @param type_: parameter type """ if (category, name) == (PARAM_KEY, PARAM_NAME): - self.updateEntityData(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile_key=profile) + self.updateCache(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile=profile) return False return True @@ -173,11 +173,11 @@ class XEP_0085(object): try: domish.generateElementsNamed(message.elements(), name="active").next() # contact enabled Chat State Notifications - self.updateEntityData(from_jid, True, profile_key=profile) + self.updateCache(from_jid, True, profile=profile) except StopIteration: if message.getAttribute('type') == 'chat': # contact didn't enable Chat State Notifications - self.updateEntityData(from_jid, False, profile_key=profile) + self.updateCache(from_jid, False, profile=profile) return True except StopIteration: pass @@ -260,7 +260,7 @@ class XEP_0085(object): except (exceptions.UnknownEntityError, KeyError): if forceEntityData: # enable it for the first time - self.updateEntityData(to_jid, True, profile_key=profile) + self.updateCache(to_jid, True, profile=profile) return True # wait for the first message before sending states return False From 8c46a238694a6868847eb0f5dcd56e4316c804c6 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 20:39:33 +0100 Subject: [PATCH 10/11] quick_frontend (quick_app): addListener takes a profiles_filter argument to check the profile before calling a listener (profile must be passed right after the event type in callListeners) --- frontends/src/quick_frontend/quick_app.py | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index a9c180d5..cd06bd3c 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -17,17 +17,19 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from sat.core.i18n import _ -import sys from sat.core.log import getLogger log = getLogger(__name__) + +from sat.core.i18n import _ from sat.core import exceptions + from sat_frontends.tools import jid from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager from sat_frontends.quick_frontend import quick_chat - from sat_frontends.quick_frontend.constants import Const as C +import sys +from collections import OrderedDict try: # FIXME: to be removed when an acceptable solution is here @@ -294,17 +296,21 @@ class QuickApp(object): handler(*args, **kwargs) self.bridge.register(functionName, signalReceived, iface) - def addListener(self, type_, callback): + def addListener(self, type_, callback, profiles_filter=None): """Add a listener for an event /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget) @param type_: type of event, can be: - avatar: called when avatar data is updated - args: (entity, avatar file, profile) + args: (entity, avatar file) + - presence: called when a presence is received + args: (entity, show, priority, statuses) @param callback: method to call on event + @param profiles_filter (set[unicode]): if set and not empty, the + listener will be callable only by one of the given profiles. """ assert type_ in C.LISTENERS - self._listeners.setdefault(type_, []).append(callback) + self._listeners.setdefault(type_, OrderedDict())[callback] = profiles_filter def removeListener(self, type_, callback): """Remove a callback from listeners @@ -313,12 +319,15 @@ class QuickApp(object): @param callback: callback to remove """ assert type_ in C.LISTENERS - self._listeners[type_].remove(callback) + self._listeners[type_].pop(callback) - def callListeners(self, type_, *args): - """Call all methods which listen of type_ event + def callListeners(self, type_, profile, *args): + """Call the methods which listen type_ event. If a profiles filter has + been register with a listener and profile argument is not None, the + listener will be called only if profile is in the profiles filter list. @param type_: same as for [addListener] + @param profile (unicode): %(doc_profile)s @param *args: arguments sent to callback """ assert type_ in C.LISTENERS @@ -327,8 +336,9 @@ class QuickApp(object): except KeyError: pass else: - for listener in listeners: - listener(*args) + for listener, profiles_filter in listeners.iteritems(): + if profile is None or not profiles_filter or profile in profiles_filter: + listener(*args) def check_profile(self, profile): """Tell if the profile is currently followed by the application""" @@ -463,7 +473,7 @@ class QuickApp(object): # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare) self.contact_lists[profile].updatePresence(entity, show, priority, statuses) - self.callListeners('presence', entity, show, priority, statuses, profile) + self.callListeners('presence', profile, entity, show, priority, statuses) def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile): """Called when a MUC room is joined""" @@ -656,7 +666,7 @@ class QuickApp(object): if entity in self.contact_lists[profile]: def gotFilename(filename): self.contact_lists[profile].setCache(entity, 'avatar', filename) - self.callListeners('avatar', entity, filename, profile) + self.callListeners('avatar', profile, entity, filename) self.bridge.getAvatarFile(value, callback=gotFilename) def askConfirmationHandler(self, confirm_id, confirm_type, data, profile): From b836fef4dd84acf4feaf1feca2158730b991e394 Mon Sep 17 00:00:00 2001 From: souliane Date: Sun, 22 Feb 2015 21:30:20 +0100 Subject: [PATCH 11/11] quick_frontend, primitivus: use a listener to update the contact list when receiving a presence --- frontends/src/primitivus/primitivus | 1 - frontends/src/quick_frontend/quick_app.py | 19 ++++++++++++++++--- .../src/quick_frontend/quick_contact_list.py | 8 ++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontends/src/primitivus/primitivus b/frontends/src/primitivus/primitivus index bbb9b326..4e727104 100755 --- a/frontends/src/primitivus/primitivus +++ b/frontends/src/primitivus/primitivus @@ -464,7 +464,6 @@ class PrimitivusApp(QuickApp, InputHistory): 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))) - self.contact_lists[profile] = contact_list return contact_list def plugging_profiles(self): diff --git a/frontends/src/quick_frontend/quick_app.py b/frontends/src/quick_frontend/quick_app.py index cd06bd3c..0f28c1e8 100644 --- a/frontends/src/quick_frontend/quick_app.py +++ b/frontends/src/quick_frontend/quick_app.py @@ -84,11 +84,14 @@ class ProfileManager(object): def _plug_profile_gotCachedValues(self, cached_values): # TODO: watched plugin + + # add the contact list and its listener contact_list = self.host.addContactList(self.profile) + self.host.contact_lists[self.profile] = contact_list for entity, data in cached_values.iteritems(): for key, value in data.iteritems(): - self.host.contact_lists[self.profile].setCache(jid.JID(entity), key, value) + contact_list.setCache(jid.JID(entity), key, value) if not self.bridge.isConnected(self.profile): self.host.setStatusOnline(False, profile=self.profile) @@ -178,11 +181,18 @@ class ProfilesManager(object): def unplug(self, profile): if profile not in self._profiles: raise ValueError('The profile [{}] is not plugged'.format(profile)) + + # remove the contact list and its listener + host = self._profiles[profile].host + host.contact_lists[profile].onDelete() + del host.contact_lists[profile] + del self._profiles[profile] def chooseOneProfile(self): return self._profiles.keys()[0] + class QuickApp(object): """This class contain the main methods needed for the frontend""" @@ -391,7 +401,7 @@ class QuickApp(object): """Tell the application to not follow anymore the profile""" if not profile in self.profiles: raise ValueError("The profile [{}] is not plugged".format(profile)) - self.profiles.remove(profile) + self.profiles.unplug(profile) def clear_profile(self): self.profiles.clear() @@ -472,7 +482,6 @@ class QuickApp(object): # if entity.bare in self.profiles[profile].data.get('watched',[]) and not entity.bare in self.profiles[profile]['onlineContact']: # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare) - self.contact_lists[profile].updatePresence(entity, show, priority, statuses) self.callListeners('presence', profile, entity, show, priority, statuses) def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile): @@ -690,8 +699,12 @@ class QuickApp(object): def onExit(self): """Must be called when the frontend is terminating""" + to_unplug = [] for profile in self.profiles: if self.bridge.isConnected(profile): if C.bool(self.bridge.getParamA("autodisconnect", "Connection", profile_key=profile)): #The user wants autodisconnection self.bridge.disconnect(profile) + to_unplug.append(profile) + for profile in to_unplug: + self.unplug_profile(profile) diff --git a/frontends/src/quick_frontend/quick_contact_list.py b/frontends/src/quick_frontend/quick_contact_list.py index f75bc3bf..daeed69b 100644 --- a/frontends/src/quick_frontend/quick_contact_list.py +++ b/frontends/src/quick_frontend/quick_contact_list.py @@ -71,6 +71,10 @@ class QuickContactList(QuickWidget): 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.updatePresence + self.host.addListener('presence', self.presenceListener, [profile]) + def __contains__(self, entity): """Check if entity is in contact list @@ -418,3 +422,7 @@ class QuickContactList(QuickWidget): return self.show_resources = show self.update() + + def onDelete(self): + QuickWidget.onDelete(self) + self.host.removeListener('presence', self.presenceListener)