diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 1391aac39..1138a0bfe 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -19,7 +19,6 @@ const SUMMARY_TIMEOUT = 1; const HIDE_TIMEOUT = 0.2; -const ICON_SIZE = 24; const BUTTON_ICON_SIZE = 36; const MAX_SOURCE_TITLE_WIDTH = 180; @@ -46,10 +45,14 @@ function _cleanMarkup(text) { // @banner: the banner text // @params: optional additional params // -// Creates a notification. In banner mode, it will show -// @source's icon, @title (in bold) and @banner, all on a single line +// Creates a notification. In banner mode, it will show an +// icon, @title (in bold) and @banner, all on a single line // (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 // notification can be expanded by moving the pointer into it. In // expanded mode, the banner text disappears, and there can be one or @@ -150,6 +153,7 @@ Notification.prototype = { update: function(title, banner, params) { params = Params.parse(params, { bannerBody: this._bannerBody, body: null, + icon: null, clear: false }); if (this._icon) @@ -167,7 +171,7 @@ Notification.prototype = { 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, col: 0, x_expand: false, @@ -560,21 +564,30 @@ Notification.prototype = { }; Signals.addSignalMethods(Notification.prototype); -function Source(title, createIcon) { - this._init(title, createIcon); +function Source(title) { + this._init(title); } Source.prototype = { - _init: function(title, createIcon) { + ICON_SIZE: 24, + + _init: function(title) { this.title = title; - if (createIcon) - this.createIcon = createIcon; + this._iconBin = new St.Bin({ width: this.ICON_SIZE, + height: this.ICON_SIZE }); }, - // This can be overridden by a subclass, or by the createIcon - // parameter to _init() - createIcon: function(size) { - throw new Error('no implementation of createIcon in ' + this); + // Called to create a new icon actor (of size this.ICON_SIZE). + // Must be overridden by the subclass if you do not pass icons + // explicitly to the Notification() constructor. + 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) { @@ -600,6 +613,15 @@ Source.prototype = { destroy: function() { 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); @@ -626,7 +648,7 @@ SummaryItem.prototype = { this._sourceBox.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); 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._sourceTitle = new St.Label({ style_class: 'source-title', text: source.title }); diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index f4a3441db..3081f8709 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -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, actions, hints, timeout) { let source = this._sources[appName]; @@ -164,7 +195,7 @@ NotificationDaemon.prototype = { // been acknowledged. if (source == null) { let title = appNameMap[appName] || appName; - source = new Source(title, icon, hints); + source = new Source(title); Main.messageTray.add(source); this._sources[appName] = source; @@ -184,8 +215,6 @@ NotificationDaemon.prototype = { if (app) source.setApp(app); }); - } else { - source.update(icon, hints); } summary = GLib.markup_escape_text(summary, -1); @@ -205,10 +234,12 @@ NotificationDaemon.prototype = { notification = this._currentNotifications[id]; } + let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE); if (notification == null) { id = nextNotificationId++; notification = new MessageTray.Notification(source, summary, body, - { bannerBody: true }); + { bannerBody: true, + icon: iconActor }); this._currentNotifications[id] = notification; notification.connect('dismissed', Lang.bind(this, function(n) { @@ -220,7 +251,8 @@ NotificationDaemon.prototype = { })); notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id)); } else { - notification.update(summary, body, { clear: true }); + notification.update(summary, body, { icon: iconActor, + clear: true }); } if (actions.length) { @@ -230,7 +262,8 @@ NotificationDaemon.prototype = { notification.setUrgent(hints.urgency == Urgency.CRITICAL); - source.notify(notification); + let sourceIconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE); + source.notify(sourceIconActor, notification); return id; }, @@ -300,57 +333,23 @@ NotificationDaemon.prototype = { DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface); -function Source(title, icon, hints) { - this._init(title, icon, hints); +function Source(title) { + this._init(title); } Source.prototype = { __proto__: MessageTray.Source.prototype, - _init: function(title, icon, hints) { + _init: function(title) { MessageTray.Source.prototype._init.call(this, title); this.app = null; this._openAppRequested = false; - - this.update(icon, hints); }, - update: function(icon, hints) { - this._icon = icon; - this._iconData = hints.icon_data; - 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); - } + notify: function(icon, notification) { + this._setSummaryIcon(icon); + MessageTray.Source.prototype.notify.call(this, notification); }, clicked: function() { diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js index 49504faad..c9d5b2448 100644 --- a/js/ui/telepathyClient.js +++ b/js/ui/telepathyClient.js @@ -475,10 +475,13 @@ Source.prototype = { this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._messageReceived)); this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages)); + + this._setSummaryIcon(this.createNotificationIcon()); }, - createIcon: function(size) { - return contactManager.createAvatar(this._conn, this._targetHandle, size); + createNotificationIcon: function() { + return contactManager.createAvatar(this._conn, this._targetHandle, + this.ICON_SIZE); }, clicked: function() { diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js index 79a86dbb2..85a16e770 100644 --- a/js/ui/windowAttentionHandler.js +++ b/js/ui/windowAttentionHandler.js @@ -94,10 +94,11 @@ Source.prototype = { MessageTray.Source.prototype._init.call(this, app.get_name()); this._window = window; this._app = app; + this._setSummaryIcon(this.createNotificationIcon()); }, - createIcon : function(size) { - return this._app.create_icon_texture(size); + createNotificationIcon : function() { + return this._app.create_icon_texture(this.ICON_SIZE); }, clicked : function() {