quick_frontend, primitivus: notifications refactoring
replaced old "alerts" system by a more generic one which use listeners and can activate callbacks on notification click.
This commit is contained in:
parent
22d70f23c4
commit
b388d2b172
8 changed files with 131 additions and 139 deletions
|
|
@ -31,8 +31,8 @@ class Const(constants.Const):
|
|||
('selected_focus', 'default,bold', 'dark red'),
|
||||
('default', 'default', 'default'),
|
||||
('default_focus', 'default,bold', 'default'),
|
||||
('alert', 'default,underline', 'default'),
|
||||
('alert_focus', 'default,bold,underline', 'default'),
|
||||
('cl_notifs', 'default,underline', 'yellow'),
|
||||
('cl_notifs_focus', 'default,bold,underline', 'yellow'),
|
||||
# Messages
|
||||
('date', 'light gray', 'default'),
|
||||
('my_nick', 'dark red,bold', 'default'),
|
||||
|
|
@ -100,6 +100,3 @@ class Const(constants.Const):
|
|||
MODE_COMMAND = 'COMMAND'
|
||||
|
||||
GROUP_DATA_FOLDED = 'folded'
|
||||
|
||||
# contacts and contact list
|
||||
ALERT_HEADER='(%i) '
|
||||
|
|
|
|||
|
|
@ -144,18 +144,16 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
"""Method called when a contact is clicked
|
||||
|
||||
@param use_bare_jid: True if use_bare_jid is set in self._buildEntityWidget.
|
||||
If True, all jids in self._alerts with the same bare jid has contact_wid.data will be removed
|
||||
@param contact_wid: widget of the contact, must have the entity set in data attribute
|
||||
@param selected: boolean returned by the widget, telling if it is selected
|
||||
"""
|
||||
entity = contact_wid.data
|
||||
self.contact_list.removeAlerts(entity, use_bare_jid)
|
||||
self.host.modeHint(C.MODE_INSERTION)
|
||||
self._emit('click', entity)
|
||||
|
||||
# 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):
|
||||
def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_notifs=True, with_show_attr=True, markup_prepend=None, markup_append = None):
|
||||
"""Build one contact markup data
|
||||
|
||||
@param entity (jid.JID): entity to build
|
||||
|
|
@ -164,8 +162,8 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
If key starts with "cache_", it will be checked in cache,
|
||||
else, getattr will be done on entity with the key (e.g. getattr(entity, 'node')).
|
||||
If nothing full or keys is None, full entity is used.
|
||||
@param use_bare_jid (bool): if True, use bare jid for alerts and selected comparisons
|
||||
@param with_alert (bool): if True, show alert if entity is in self._alerts
|
||||
@param 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
|
||||
|
|
@ -199,12 +197,12 @@ class ContactList(PrimitivusWidget, QuickContactList):
|
|||
else:
|
||||
entity_attr = 'default'
|
||||
|
||||
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
|
||||
notifs = self.host.getNotifs(entity.bare, profile=self.profile)
|
||||
if notifs:
|
||||
entity_attr = 'cl_notifs'
|
||||
header = u'({}) '.format(len(notifs))
|
||||
else:
|
||||
header = ''
|
||||
header = u''
|
||||
|
||||
markup.append((entity_attr, entity_txt))
|
||||
if markup_prepend:
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ class TarotGame(QuickTarotGame, urwid.WidgetWrap):
|
|||
QuickTarotGame.tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards)
|
||||
self.hand_wid.update(self.hand)
|
||||
if self._autoplay == None: # No dialog if there is autoplay
|
||||
self.parent.host.notify(_('Cards played are invalid !'))
|
||||
self.parent.host.barNotify(_('Cards played are invalid !'))
|
||||
self.parent.host.redraw()
|
||||
|
||||
def tarotGameCardsPlayedHandler(self, player, cards):
|
||||
|
|
|
|||
|
|
@ -586,11 +586,24 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
else:
|
||||
self.notif_bar.addPopUp(pop_up_widget)
|
||||
|
||||
def notify(self, message):
|
||||
def barNotify(self, message):
|
||||
""""Notify message to user via notification bar"""
|
||||
self.notif_bar.addMessage(message)
|
||||
self.redraw()
|
||||
|
||||
def notify(self, type_, entity=None, message=None, subject=None, callback=None, cb_args=None, widget=None, profile=C.PROF_KEY_NONE):
|
||||
if widget is None or widget is not None and widget != self.selected_widget:
|
||||
# we ignore notification if the widget is selected but we can
|
||||
# still do a desktop notification is the X window has not the focus
|
||||
super(PrimitivusApp, self).notify(type_, entity, message, subject, callback, cb_args, widget, profile)
|
||||
if not self.x_notify.hasFocus():
|
||||
if message is None:
|
||||
message = _("{app}: a new event has just happened{entity}").format(
|
||||
app=C.APP_NAME,
|
||||
entity=u' ({})'.format(entity) if entity else '')
|
||||
self.x_notify.sendNotification(message)
|
||||
|
||||
|
||||
def newWidget(self, widget):
|
||||
if self.selected_widget is None:
|
||||
self.selectWidget(widget)
|
||||
|
|
@ -651,6 +664,7 @@ class PrimitivusApp(QuickApp, InputHistory):
|
|||
# we have clicked on a private MUC conversation
|
||||
chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, force_hash = Chat.getPrivateHash(contact_list.profile, entity), profile=contact_list.profile)
|
||||
else:
|
||||
self.clearNotifs(entity, profile=contact_list.profile)
|
||||
chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, profile=contact_list.profile)
|
||||
self.selectWidget(chat_widget)
|
||||
self.menu_roller.addMenu(_('Chat menu'), chat_widget.getMenu(), C.MENU_ID_WIDGET)
|
||||
|
|
|
|||
|
|
@ -63,10 +63,6 @@ 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"
|
||||
|
|
@ -86,4 +82,10 @@ class Const(constants.Const):
|
|||
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'}
|
||||
LISTENERS = {'avatar', 'nick', 'presence', 'profilePlugged', 'disconnect', 'gotMenus', 'menu', 'notification', 'notificationsClear'}
|
||||
|
||||
# Notifications
|
||||
NOTIFY_MESSAGE = 'MESSAGE' # a message was received
|
||||
NOTIFY_MENTION = 'MENTION' # user was mentionned
|
||||
NOTIFY_PROGRESS_END = 'PROGRESS_END' # a progression has finised
|
||||
NOTIFY_GENERIC = 'GENERIC' # a notification which has not its own type
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from sat_frontends.quick_frontend.constants import Const as C
|
|||
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
import time
|
||||
|
||||
try:
|
||||
# FIXME: to be removed when an acceptable solution is here
|
||||
|
|
@ -51,13 +52,7 @@ class ProfileManager(object):
|
|||
def __init__(self, profile):
|
||||
self.profile = profile
|
||||
self.whoami = None
|
||||
self.data = {}
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.data[key] = value
|
||||
self.notifications = {} # key: bare jid or '' for general, value: notif data
|
||||
|
||||
def plug(self):
|
||||
"""Plug the profile to the host"""
|
||||
|
|
@ -268,6 +263,8 @@ class QuickApp(object):
|
|||
|
||||
self.current_action_ids = set() # FIXME: to be removed
|
||||
self.current_action_ids_cb = {} # FIXME: to be removed
|
||||
self._notif_id = 0
|
||||
self._notifications = OrderedDict()
|
||||
self.media_dir = self.bridge.getConfig('', 'media_dir')
|
||||
self.features = None
|
||||
|
||||
|
|
@ -284,12 +281,6 @@ class QuickApp(object):
|
|||
"""widgets currently visible (must be implemented by frontend)"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def alerts_count(self):
|
||||
"""Count the over whole alerts for all contact lists"""
|
||||
# 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
|
||||
|
||||
|
|
@ -330,6 +321,10 @@ class QuickApp(object):
|
|||
args: (entity, new_nick, profile)
|
||||
- presence: called when a presence is received
|
||||
args: (entity, show, priority, statuses, profile)
|
||||
- notification: called when a new notification is emited
|
||||
args: (entity, notification_data, profile)
|
||||
- notification_clear: called when notifications are cleared
|
||||
args: (entity, type_, profile)
|
||||
- menu: called when a menu item is added or removed
|
||||
args: (type_, path, path_i18n, item) were values are:
|
||||
type_: same as in [sat.core.sat_main.SAT.importMenu]
|
||||
|
|
@ -496,19 +491,6 @@ class QuickApp(object):
|
|||
|
||||
chat_widget.messageNew(uid, timestamp, from_jid, target, msg, subject, type_, extra, profile)
|
||||
|
||||
# ContactList alert
|
||||
if not from_me:
|
||||
visible = False
|
||||
for widget in self.visible_widgets:
|
||||
if isinstance(widget, quick_chat.QuickChat) and widget.manageMessage(from_jid, type_):
|
||||
visible = True
|
||||
break
|
||||
if visible: # FIXME: à virer gof:
|
||||
if self.isHidden(): # the window is hidden
|
||||
self.updateAlertsCounter(extra_inc=1)
|
||||
else:
|
||||
contact_list.addAlert(from_jid.bare if type_ == C.MESS_TYPE_GROUPCHAT else from_jid)
|
||||
|
||||
def messageSend(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
|
||||
if subject is None:
|
||||
subject = {}
|
||||
|
|
@ -602,6 +584,76 @@ class QuickApp(object):
|
|||
# contact_list.setCache(from_jid, 'chat_state', to_display)
|
||||
# widget.update(from_jid)
|
||||
|
||||
def notify(self, type_, entity=None, message=None, subject=None, callback=None, cb_args=None, widget=None, profile=C.PROF_KEY_NONE):
|
||||
"""Trigger an event notification
|
||||
|
||||
@param type_(unicode): notifation kind,
|
||||
one of C.NOTIFY_* constant or any custom type specific to frontend
|
||||
@param entity(jid.JID, None): entity involved in the notification
|
||||
if entity is in contact list, a indicator may be added in front of it
|
||||
@param message(unicode, None): message of the notification
|
||||
@param subject(unicode, None): subject of the notification
|
||||
@param callback(callable, None): method to call when notification is selected
|
||||
@param cb_args(list, None): list of args for callback
|
||||
@param widget(object, None): widget where the notification happened
|
||||
"""
|
||||
notif_dict = self.profiles[profile].notifications
|
||||
key = '' if entity is None else entity.bare
|
||||
type_notifs = notif_dict.setdefault(key, {}).setdefault(type_, [])
|
||||
notif_data = {
|
||||
'id': self._notif_id,
|
||||
'time': time.time(),
|
||||
'entity': entity,
|
||||
'callback': callback,
|
||||
'cb_args': cb_args,
|
||||
'message': message,
|
||||
'subject': subject,
|
||||
}
|
||||
if widget is not None:
|
||||
notif_data[widget] = widget
|
||||
type_notifs.append(notif_data)
|
||||
self._notifications[self._notif_id] = notif_data
|
||||
self.callListeners('notification', entity, notif_data, profile=profile)
|
||||
|
||||
def getNotifs(self, entity, type_=None, profile=C.PROF_KEY_NONE):
|
||||
"""return notifications for given entity
|
||||
|
||||
@param entity(jid.JID, None): bare jid of the entity to check
|
||||
None to get general notifications
|
||||
@param type_(unicode, None): notification type to filter
|
||||
None to get all notifications
|
||||
@return (list[dict]): list of notifications
|
||||
"""
|
||||
notif_dict = self.profiles[profile].notifications
|
||||
key = '' if entity is None else entity.bare
|
||||
key_notifs = notif_dict.setdefault(key, {})
|
||||
if type_ is not None:
|
||||
return key_notifs.get(type_, [])
|
||||
ret = []
|
||||
for notifs_list in key_notifs.itervalues():
|
||||
ret.extend(notifs_list)
|
||||
return ret
|
||||
|
||||
def clearNotifs(self, entity, type_=None, profile=C.PROF_KEY_NONE):
|
||||
"""return notifications for given entity
|
||||
|
||||
@param entity(jid.JID, None): bare jid of the entity to check
|
||||
None to clear general notifications (but keep entities ones)
|
||||
@param type_(unicode, None): notification type to filter
|
||||
None to clear all notifications
|
||||
@return (list[dict]): list of notifications
|
||||
"""
|
||||
notif_dict = self.profiles[profile].notifications
|
||||
key = '' if entity is None else entity.bare
|
||||
try:
|
||||
if type_ is None:
|
||||
del notif_dict[key]
|
||||
else:
|
||||
del notif_dict[key][type_]
|
||||
except KeyError:
|
||||
return
|
||||
self.callListeners('notificationsClear', entity, type_, profile=profile)
|
||||
|
||||
def psEventHandler(self, category, service_s, node, event_type, data, profile):
|
||||
"""Called when a PubSub event is received.
|
||||
|
||||
|
|
@ -688,13 +740,6 @@ class QuickApp(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def updateAlertsCounter(self, extra_inc=0):
|
||||
"""Update the over whole alerts counter.
|
||||
|
||||
@param extra_inc (int): extra counter
|
||||
"""
|
||||
pass
|
||||
|
||||
def paramUpdateHandler(self, name, value, namespace, profile):
|
||||
log.debug(_(u"param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value})
|
||||
if (namespace, name) == ("Connection", "JabberID"):
|
||||
|
|
|
|||
|
|
@ -340,15 +340,6 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def notify(self, contact="somebody", msg=""):
|
||||
"""Notify the user of a new message if the frontend doesn't have the focus.
|
||||
|
||||
@param contact (unicode): contact who wrote to the users
|
||||
@param msg (unicode): the message that has been received
|
||||
"""
|
||||
# FIXME: not called anymore after refactoring
|
||||
raise NotImplemented
|
||||
|
||||
def printDayChange(self, day):
|
||||
"""Display the day on a new line.
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +68,6 @@ class ProfileContactList(object):
|
|||
# contacts in roster (bare jids)
|
||||
self._roster = set()
|
||||
|
||||
# alerts per entity (key: full jid, value: list of alerts)
|
||||
self._alerts = {}
|
||||
|
||||
# selected entities, full jid
|
||||
self._selected = set()
|
||||
|
||||
|
|
@ -91,6 +88,10 @@ class ProfileContactList(object):
|
|||
self.host.addListener('presence', self.presenceListener, [self.profile])
|
||||
self.nickListener = self.onNickUpdate
|
||||
self.host.addListener('nick', self.nickListener, [self.profile])
|
||||
self.notifListener = self.onNotification
|
||||
self.host.addListener('notification', self.notifListener, [self.profile])
|
||||
# notifListener only update the entity, so we can re-use it
|
||||
self.host.addListener('notificationsClear', self.notifListener, [self.profile])
|
||||
|
||||
def _showEmptyGroups(self, show_str):
|
||||
# Called only by __init__
|
||||
|
|
@ -321,8 +322,6 @@ class ProfileContactList(object):
|
|||
self._specials.clear()
|
||||
self._special_extras.clear()
|
||||
self._roster.clear()
|
||||
self._alerts.clear()
|
||||
self.host.updateAlertsCounter()
|
||||
self.update()
|
||||
|
||||
def setContact(self, entity, groups=None, attributes=None, in_roster=False):
|
||||
|
|
@ -392,15 +391,14 @@ class ProfileContactList(object):
|
|||
show = self.getCache(entity, C.PRESENCE_SHOW)
|
||||
|
||||
if check_resource:
|
||||
alerts = self._alerts.keys()
|
||||
selected = self._selected
|
||||
else:
|
||||
alerts = {alert.bare for alert in self._alerts}
|
||||
selected = {selected.bare for selected in self._selected}
|
||||
return ((show is not None and show != C.PRESENCE_UNAVAILABLE)
|
||||
or self.show_disconnected
|
||||
or entity in alerts
|
||||
or entity in selected)
|
||||
or entity in selected
|
||||
or self.host.getNotifs(entity.bare, profile=self.profile)
|
||||
)
|
||||
|
||||
def anyEntityToShow(self, entities, check_resources=False):
|
||||
"""Tell if in a list of entities, at least one should be shown
|
||||
|
|
@ -443,16 +441,12 @@ class ProfileContactList(object):
|
|||
self._groups[group]['jids'].remove(entity_bare)
|
||||
if not self._groups[group]['jids']:
|
||||
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):
|
||||
for iterable in (self._selected, self._specials, self._special_extras):
|
||||
to_remove = set()
|
||||
for set_entity in iterable:
|
||||
if set_entity.bare == entity.bare:
|
||||
to_remove.add(set_entity)
|
||||
if isinstance(iterable, set):
|
||||
iterable.difference_update(to_remove)
|
||||
else: # XXX: self._alerts is a dict
|
||||
for item in to_remove:
|
||||
del iterable[item]
|
||||
iterable.difference_update(to_remove)
|
||||
self.update([entity], C.UPDATE_DELETE, self.profile)
|
||||
|
||||
def onPresenceUpdate(self, entity, show, priority, statuses, profile):
|
||||
|
|
@ -500,6 +494,17 @@ class ProfileContactList(object):
|
|||
self.setCache(entity, 'nick', new_nick)
|
||||
self.update([entity], C.UPDATE_MODIFY, profile)
|
||||
|
||||
def onNotification(self, entity, notif, profile):
|
||||
"""Update entity with notification
|
||||
|
||||
@param entity(jid.JID): entity updated
|
||||
@param notif(dict): notification data
|
||||
@param profile: %(doc_profile)s
|
||||
"""
|
||||
assert profile == self.profile
|
||||
if entity is not None:
|
||||
self.update([entity], C.UPDATE_MODIFY, profile)
|
||||
|
||||
def unselect(self, entity):
|
||||
"""Unselect an entity
|
||||
|
||||
|
|
@ -540,66 +545,6 @@ class ProfileContactList(object):
|
|||
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.
|
||||
|
||||
@param entity (jid.JID): entity
|
||||
@param use_bare_jid (bool): if True, cumulate the alerts of all the resources sharing the same bare JID
|
||||
@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
|
||||
"""
|
||||
return [] # FIXME: temporarily disabled
|
||||
if not use_bare_jid:
|
||||
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, type_=None):
|
||||
"""Add an alert for this enity
|
||||
|
||||
@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, [])
|
||||
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).
|
||||
|
||||
@param entity(jid.JID): entity (resource is significant)
|
||||
@param use_bare_jid (bool): if True, ignore the resource
|
||||
"""
|
||||
if use_bare_jid:
|
||||
to_remove = set()
|
||||
for alert_entity in self._alerts:
|
||||
if alert_entity.bare == entity.bare:
|
||||
to_remove.add(alert_entity)
|
||||
if not to_remove:
|
||||
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
|
||||
else:
|
||||
self.update([entity], C.UPDATE_MODIFY, self.profile)
|
||||
self.host.updateAlertsCounter() # FIXME: ?
|
||||
|
||||
def showOfflineContacts(self, show):
|
||||
"""Tell if offline contacts should shown
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue