[MessageTray] Clean up source-vs-notification icon handling

A Source needs exactly one summary icon (which in the case of a
trayicon-based source won't even be just an image), but possibly many
notification icons, which may vary for successive notifications
(particularly in the case of NotificationDaemon notifications). So
differentiate these cases in the API.

https://bugzilla.gnome.org/show_bug.cgi?id=627303
This commit is contained in:
Dan Winship 2010-08-05 13:09:27 -04:00
parent 1951812a47
commit 6f57f07214
4 changed files with 89 additions and 64 deletions

View File

@ -19,7 +19,6 @@ const SUMMARY_TIMEOUT = 1;
const HIDE_TIMEOUT = 0.2; const HIDE_TIMEOUT = 0.2;
const ICON_SIZE = 24;
const BUTTON_ICON_SIZE = 36; const BUTTON_ICON_SIZE = 36;
const MAX_SOURCE_TITLE_WIDTH = 180; const MAX_SOURCE_TITLE_WIDTH = 180;
@ -46,10 +45,14 @@ function _cleanMarkup(text) {
// @banner: the banner text // @banner: the banner text
// @params: optional additional params // @params: optional additional params
// //
// Creates a notification. In banner mode, it will show // Creates a notification. In banner mode, it will show an
// @source's icon, @title (in bold) and @banner, all on a single line // icon, @title (in bold) and @banner, all on a single line
// (with @banner ellipsized if necessary). // (with @banner ellipsized if necessary).
// //
// By default, the icon shown is created by calling
// source.createNotificationIcon(). However, you can override this
// by passing an 'icon' parameter in @params.
//
// Additional notification details can be added, in which case the // Additional notification details can be added, in which case the
// notification can be expanded by moving the pointer into it. In // notification can be expanded by moving the pointer into it. In
// expanded mode, the banner text disappears, and there can be one or // expanded mode, the banner text disappears, and there can be one or
@ -150,6 +153,7 @@ Notification.prototype = {
update: function(title, banner, params) { update: function(title, banner, params) {
params = Params.parse(params, { bannerBody: this._bannerBody, params = Params.parse(params, { bannerBody: this._bannerBody,
body: null, body: null,
icon: null,
clear: false }); clear: false });
if (this._icon) if (this._icon)
@ -167,7 +171,7 @@ Notification.prototype = {
this._bannerBody = params.bannerBody; this._bannerBody = params.bannerBody;
this._icon = this.source.createIcon(ICON_SIZE); this._icon = params.icon || this.source.createNotificationIcon();
this.actor.add(this._icon, { row: 0, this.actor.add(this._icon, { row: 0,
col: 0, col: 0,
x_expand: false, x_expand: false,
@ -560,21 +564,30 @@ Notification.prototype = {
}; };
Signals.addSignalMethods(Notification.prototype); Signals.addSignalMethods(Notification.prototype);
function Source(title, createIcon) { function Source(title) {
this._init(title, createIcon); this._init(title);
} }
Source.prototype = { Source.prototype = {
_init: function(title, createIcon) { ICON_SIZE: 24,
_init: function(title) {
this.title = title; this.title = title;
if (createIcon) this._iconBin = new St.Bin({ width: this.ICON_SIZE,
this.createIcon = createIcon; height: this.ICON_SIZE });
}, },
// This can be overridden by a subclass, or by the createIcon // Called to create a new icon actor (of size this.ICON_SIZE).
// parameter to _init() // Must be overridden by the subclass if you do not pass icons
createIcon: function(size) { // explicitly to the Notification() constructor.
throw new Error('no implementation of createIcon in ' + this); createNotificationIcon: function() {
throw new Error('no implementation of createNotificationIcon in ' + this);
},
// Unlike createNotificationIcon, this always returns the same actor;
// there is only one summary icon actor for a Source.
getSummaryIcon: function() {
return this._iconBin;
}, },
notify: function(notification) { notify: function(notification) {
@ -600,6 +613,15 @@ Source.prototype = {
destroy: function() { destroy: function() {
this.emit('destroy'); this.emit('destroy');
},
//// Protected methods ////
// The subclass must call this at least once to set the summary icon.
_setSummaryIcon: function(icon) {
if (this._iconBin.child)
this._iconBin.child.destroy();
this._iconBin.child = icon;
} }
}; };
Signals.addSignalMethods(Source.prototype); Signals.addSignalMethods(Source.prototype);
@ -626,7 +648,7 @@ SummaryItem.prototype = {
this._sourceBox.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); this._sourceBox.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this._sourceBox.connect('allocate', Lang.bind(this, this._allocate)); this._sourceBox.connect('allocate', Lang.bind(this, this._allocate));
this._sourceIcon = source.createIcon(ICON_SIZE); this._sourceIcon = source.getSummaryIcon();
this._sourceTitleBin = new St.Bin({ y_align: St.Align.MIDDLE, x_fill: true }); this._sourceTitleBin = new St.Bin({ y_align: St.Align.MIDDLE, x_fill: true });
this._sourceTitle = new St.Label({ style_class: 'source-title', this._sourceTitle = new St.Label({ style_class: 'source-title',
text: source.title }); text: source.title });

View File

@ -141,6 +141,37 @@ NotificationDaemon.prototype = {
} }
}, },
_iconForNotificationData: function(icon, hints, size) {
let textureCache = St.TextureCache.get_default();
if (icon) {
if (icon.substr(0, 7) == 'file://')
return textureCache.load_uri_async(icon, size, size);
else if (icon[0] == '/') {
let uri = GLib.filename_to_uri(icon, null);
return textureCache.load_uri_async(uri, size, size);
} else
return textureCache.load_icon_name(icon, size);
} else if (hints.icon_data) {
let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = hints.icon_data;
return textureCache.load_from_raw(data, data.length, hasAlpha,
width, height, rowStride, size);
} else {
let stockIcon;
switch (hints.urgency) {
case Urgency.LOW:
case Urgency.NORMAL:
stockIcon = 'gtk-dialog-info';
break;
case Urgency.CRITICAL:
stockIcon = 'gtk-dialog-error';
break;
}
return textureCache.load_icon_name(stockIcon, size);
}
},
Notify: function(appName, replacesId, icon, summary, body, Notify: function(appName, replacesId, icon, summary, body,
actions, hints, timeout) { actions, hints, timeout) {
let source = this._sources[appName]; let source = this._sources[appName];
@ -164,7 +195,7 @@ NotificationDaemon.prototype = {
// been acknowledged. // been acknowledged.
if (source == null) { if (source == null) {
let title = appNameMap[appName] || appName; let title = appNameMap[appName] || appName;
source = new Source(title, icon, hints); source = new Source(title);
Main.messageTray.add(source); Main.messageTray.add(source);
this._sources[appName] = source; this._sources[appName] = source;
@ -184,8 +215,6 @@ NotificationDaemon.prototype = {
if (app) if (app)
source.setApp(app); source.setApp(app);
}); });
} else {
source.update(icon, hints);
} }
summary = GLib.markup_escape_text(summary, -1); summary = GLib.markup_escape_text(summary, -1);
@ -205,10 +234,12 @@ NotificationDaemon.prototype = {
notification = this._currentNotifications[id]; notification = this._currentNotifications[id];
} }
let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
if (notification == null) { if (notification == null) {
id = nextNotificationId++; id = nextNotificationId++;
notification = new MessageTray.Notification(source, summary, body, notification = new MessageTray.Notification(source, summary, body,
{ bannerBody: true }); { bannerBody: true,
icon: iconActor });
this._currentNotifications[id] = notification; this._currentNotifications[id] = notification;
notification.connect('dismissed', Lang.bind(this, notification.connect('dismissed', Lang.bind(this,
function(n) { function(n) {
@ -220,7 +251,8 @@ NotificationDaemon.prototype = {
})); }));
notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id)); notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id));
} else { } else {
notification.update(summary, body, { clear: true }); notification.update(summary, body, { icon: iconActor,
clear: true });
} }
if (actions.length) { if (actions.length) {
@ -230,7 +262,8 @@ NotificationDaemon.prototype = {
notification.setUrgent(hints.urgency == Urgency.CRITICAL); notification.setUrgent(hints.urgency == Urgency.CRITICAL);
source.notify(notification); let sourceIconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
source.notify(sourceIconActor, notification);
return id; return id;
}, },
@ -300,57 +333,23 @@ NotificationDaemon.prototype = {
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface); DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
function Source(title, icon, hints) { function Source(title) {
this._init(title, icon, hints); this._init(title);
} }
Source.prototype = { Source.prototype = {
__proto__: MessageTray.Source.prototype, __proto__: MessageTray.Source.prototype,
_init: function(title, icon, hints) { _init: function(title) {
MessageTray.Source.prototype._init.call(this, title); MessageTray.Source.prototype._init.call(this, title);
this.app = null; this.app = null;
this._openAppRequested = false; this._openAppRequested = false;
this.update(icon, hints);
}, },
update: function(icon, hints) { notify: function(icon, notification) {
this._icon = icon; this._setSummaryIcon(icon);
this._iconData = hints.icon_data; MessageTray.Source.prototype.notify.call(this, notification);
this._urgency = hints.urgency;
},
createIcon: function(size) {
let textureCache = St.TextureCache.get_default();
if (this._icon) {
if (this._icon.substr(0, 7) == 'file://')
return textureCache.load_uri_async(this._icon, size, size);
else if (this._icon[0] == '/') {
let uri = GLib.filename_to_uri(this._icon, null);
return textureCache.load_uri_async(uri, size, size);
} else
return textureCache.load_icon_name(this._icon, size);
} else if (this._iconData) {
let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = this._iconData;
return textureCache.load_from_raw(data, data.length, hasAlpha,
width, height, rowStride, size);
} else {
let stockIcon;
switch (this._urgency) {
case Urgency.LOW:
case Urgency.NORMAL:
stockIcon = 'gtk-dialog-info';
break;
case Urgency.CRITICAL:
stockIcon = 'gtk-dialog-error';
break;
}
return textureCache.load_icon_name(stockIcon, size);
}
}, },
clicked: function() { clicked: function() {

View File

@ -475,10 +475,13 @@ Source.prototype = {
this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._messageReceived)); this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._messageReceived));
this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages)); this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages));
this._setSummaryIcon(this.createNotificationIcon());
}, },
createIcon: function(size) { createNotificationIcon: function() {
return contactManager.createAvatar(this._conn, this._targetHandle, size); return contactManager.createAvatar(this._conn, this._targetHandle,
this.ICON_SIZE);
}, },
clicked: function() { clicked: function() {

View File

@ -94,10 +94,11 @@ Source.prototype = {
MessageTray.Source.prototype._init.call(this, app.get_name()); MessageTray.Source.prototype._init.call(this, app.get_name());
this._window = window; this._window = window;
this._app = app; this._app = app;
this._setSummaryIcon(this.createNotificationIcon());
}, },
createIcon : function(size) { createNotificationIcon : function() {
return this._app.create_icon_texture(size); return this._app.create_icon_texture(this.ICON_SIZE);
}, },
clicked : function() { clicked : function() {