From ac6c808124cd8786712ae92739585939e9ec4a64 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Thu, 19 Jul 2012 15:05:17 +0200 Subject: [PATCH] MessageTray: rework icon handling to split model and view To allow more than one summary icon actor for a source we split the model of the source icon (which is iconName, if the default implementation is used, or a GIcon otherwise) and replace createNotificationIcon() with a generic createIcon(size). Also, the actual source actor is split into a separate class, that handles the notification counter automatically. https://bugzilla.gnome.org/show_bug.cgi?id=619955 --- js/ui/autorunManager.js | 4 +- js/ui/messageTray.js | 91 ++++++++++++++++++++------------- js/ui/notificationDaemon.js | 59 +++++++++++---------- js/ui/telepathyClient.js | 12 ++--- js/ui/windowAttentionHandler.js | 4 +- src/shell-util.c | 31 +++++++++++ src/shell-util.h | 9 ++++ 7 files changed, 139 insertions(+), 71 deletions(-) diff --git a/js/ui/autorunManager.js b/js/ui/autorunManager.js index 099573bb9..3e8b52ead 100644 --- a/js/ui/autorunManager.js +++ b/js/ui/autorunManager.js @@ -502,9 +502,9 @@ const AutorunTransientSource = new Lang.Class({ this.notify(this._notification); }, - createNotificationIcon: function() { + createIcon: function(size) { return new St.Icon({ gicon: this.mount.get_icon(), - icon_size: this.ICON_SIZE }); + icon_size: size }); } }); diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index b50674f75..393b30586 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -394,7 +394,7 @@ Signals.addSignalMethods(FocusGrabber.prototype); // the content area (as with addBody()). // // By default, the icon shown is created by calling -// source.createNotificationIcon(). However, if @params contains an 'icon' +// source.createIcon(). However, if @params contains an 'icon' // parameter, the passed in icon will be used. // // If @params contains a 'titleMarkup', 'bannerMarkup', or @@ -539,7 +539,7 @@ const Notification = new Lang.Class({ this._table.remove_style_class_name('multi-line-notification'); if (!this._icon) { - this._icon = params.icon || this.source.createNotificationIcon(); + this._icon = params.icon || this.source.createIcon(this.source.ICON_SIZE); this._table.add(this._icon, { row: 0, col: 0, x_expand: false, @@ -1029,24 +1029,19 @@ const Notification = new Lang.Class({ }); Signals.addSignalMethods(Notification.prototype); -const Source = new Lang.Class({ - Name: 'MessageTraySource', +const SourceActor = new Lang.Class({ + Name: 'SourceActor', - ICON_SIZE: 24, - - _init: function(title, iconName, iconType) { - this.title = title; - this.iconName = iconName; - this.iconType = iconType; + _init: function(source, size) { + this._source = source; this.actor = new Shell.GenericContainer(); this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); this.actor.connect('allocate', Lang.bind(this, this._allocate)); - this.actor.connect('destroy', Lang.bind(this, - function() { - this._actorDestroyed = true; - })); + this.actor.connect('destroy', Lang.bind(this, function() { + this._actorDestroyed = true; + })); this._actorDestroyed = false; this._counterLabel = new St.Label(); @@ -1054,21 +1049,20 @@ const Source = new Lang.Class({ child: this._counterLabel }); this._counterBin.hide(); - this._iconBin = new St.Bin({ width: this.ICON_SIZE, - height: this.ICON_SIZE, + this._iconBin = new St.Bin({ width: size, + height: size, x_fill: true, y_fill: true }); this.actor.add_actor(this._iconBin); this.actor.add_actor(this._counterBin); - this.isTransient = false; - this.isChat = false; - this.isMuted = false; + this._source.connect('count-changed', Lang.bind(this, this._updateCount)); + this._updateCount(); + }, - this.notifications = []; - - this._setSummaryIcon(this.createNotificationIcon()); + setIcon: function(icon) { + this._iconBin.child = icon; }, _getPreferredWidth: function (actor, forHeight, alloc) { @@ -1106,15 +1100,44 @@ const Source = new Lang.Class({ this._counterBin.allocate(childBox, flags); }, + _updateCount: function() { + if (this._actorDestroyed) + return; + + this._counterBin.visible = this._source.countVisible; + this._counterLabel.set_text(this._source.count.toString()); + }, +}); + +const Source = new Lang.Class({ + Name: 'MessageTraySource', + + ICON_SIZE: 24, + + _init: function(title, iconName, iconType) { + this.title = title; + this.iconName = iconName; + this.iconType = iconType; + + this.count = 0; + + this.isTransient = false; + this.isChat = false; + this.isMuted = false; + + this.notifications = []; + + this.mainIcon = new SourceActor(this, this.ICON_SIZE); + this._setSummaryIcon(this.createIcon(this.ICON_SIZE)); + }, + _setCount: function(count, visible) { if (isNaN(parseInt(count))) throw new Error("Invalid notification count: " + count); - if (this._actorDestroyed) - return; - - this._counterBin.visible = visible; - this._counterLabel.set_text(count.toString()); + this.count = count; + this.countVisible = visible; + this.emit('count-changed'); }, _updateCount: function() { @@ -1138,19 +1161,19 @@ const Source = new Lang.Class({ this.emit('muted-changed'); }, - // Called to create a new icon actor (of size this.ICON_SIZE). + // Called to create a new icon actor. // Provides a sane default implementation, override if you need // something more fancy. - createNotificationIcon: function() { + createIcon: function(size) { return new St.Icon({ icon_name: this.iconName, icon_type: this.iconType, - icon_size: this.ICON_SIZE }); + icon_size: size }); }, - // Unlike createNotificationIcon, this always returns the same actor; + // Unlike createIcon, this always returns the same actor; // there is only one summary icon actor for a Source. getSummaryIcon: function() { - return this.actor; + return this.mainIcon.actor; }, pushNotification: function(notification) { @@ -1196,9 +1219,7 @@ const Source = new Lang.Class({ //// Protected methods //// _setSummaryIcon: function(icon) { - if (this._iconBin.child) - this._iconBin.child.destroy(); - this._iconBin.child = icon; + this.mainIcon.setIcon(icon); }, open: function(notification) { diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index a02898a6f..49d1e0bc1 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -1,6 +1,7 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const Clutter = imports.gi.Clutter; +const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; @@ -108,9 +109,7 @@ const NotificationDaemon = new Lang.Class({ Lang.bind(this, this._onFocusAppChanged)); }, - _iconForNotificationData: function(icon, hints, size) { - let textureCache = St.TextureCache.get_default(); - + _iconForNotificationData: function(icon, hints) { // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon // and don't show a large image. There are currently many applications that use // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets @@ -121,20 +120,18 @@ const NotificationDaemon = new Lang.Class({ // a large image. if (icon) { if (icon.substr(0, 7) == 'file://') - return textureCache.load_uri_async(icon, size, size); + return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) }); else if (icon[0] == '/') { - let uri = GLib.filename_to_uri(icon, null); - return textureCache.load_uri_async(uri, size, size); + return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) }); } else - return new St.Icon({ icon_name: icon, - icon_type: St.IconType.FULLCOLOR, - icon_size: size }); + return new Gio.ThemedIcon({ name: icon }); } else if (hints['image-data']) { let [width, height, rowStride, hasAlpha, bitsPerSample, nChannels, data] = hints['image-data']; - return textureCache.load_from_raw(data, hasAlpha, width, height, rowStride, size); + return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha, + bitsPerSample, width, height, rowStride); } else if (hints['image-path']) { - return textureCache.load_uri_async(GLib.filename_to_uri(hints['image-path'], null), size, size); + return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) }); } else { let stockIcon; switch (hints.urgency) { @@ -146,9 +143,7 @@ const NotificationDaemon = new Lang.Class({ stockIcon = 'gtk-dialog-error'; break; } - return new St.Icon({ icon_name: stockIcon, - icon_type: St.IconType.FULLCOLOR, - icon_size: size }); + return new Gio.ThemedIcon({ name: stockIcon }); } }, @@ -341,7 +336,10 @@ const NotificationDaemon = new Lang.Class({ [ndata.id, ndata.icon, ndata.summary, ndata.body, ndata.actions, ndata.hints, ndata.notification]; - let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE); + let gicon = this._iconForNotificationData(icon, hints); + let iconActor = new St.Icon({ gicon: gicon, + icon_type: St.IconType.FULLCOLOR, + icon_size: source.ICON_SIZE }); if (notification == null) { notification = new MessageTray.Notification(source, summary, body, @@ -421,8 +419,8 @@ const NotificationDaemon = new Lang.Class({ // of the 'transient' hint with hints['transient'] rather than hints.transient notification.setTransient(hints['transient'] == true); - let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null; - source.processNotification(notification, sourceIconActor); + let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null; + source.processNotification(notification, sourceGIcon); }, CloseNotification: function(id) { @@ -534,11 +532,10 @@ const Source = new Lang.Class({ this.destroy(); }, - processNotification: function(notification, icon) { - if (!this.app) - this._setApp(); - if (!this.app && icon) - this._setSummaryIcon(icon); + processNotification: function(notification, gicon) { + if (gicon) + this._gicon = gicon; + this._setSummaryIcon(this.createIcon(this.ICON_SIZE)); let tracker = Shell.WindowTracker.get_default(); if (notification.resident && this.app && tracker.focus_app == this.app) @@ -606,7 +603,7 @@ const Source = new Lang.Class({ // notification-based icons (ie, not a trayicon) or if it was unset before if (!this.trayIcon) { this.useNotificationIcon = false; - this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE)); + this._setSummaryIcon(this.createIcon(this.ICON_SIZE)); } }, @@ -640,8 +637,18 @@ const Source = new Lang.Class({ this.parent(); }, - createNotificationIcon: function() { - // We set the summary icon ourselves. - return null; + createIcon: function(size) { + if (this.trayIcon) { + return new Clutter.Clone({ width: size, + height: size, + source: this.trayIcon }); + } else if (this.app) { + return this.app.create_icon_texture(size); + } else if (this._gicon) { + return new St.Icon({ gicon: this._gicon, + icon_size: size }); + } else { + return null; + } } }); diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js index 46d7ef0e9..240969bdf 100644 --- a/js/ui/telepathyClient.js +++ b/js/ui/telepathyClient.js @@ -482,9 +482,9 @@ const ChatSource = new Lang.Class({ this._notification.appendAliasChange(oldAlias, newAlias); }, - createNotificationIcon: function() { + createIcon: function(size) { this._iconBox = new St.Bin({ style_class: 'avatar-box' }); - this._iconBox._size = this.ICON_SIZE; + this._iconBox._size = size; let textureCache = St.TextureCache.get_default(); let file = this._contact.get_avatar_file(); @@ -532,8 +532,8 @@ const ChatSource = new Lang.Class({ }, _updateAvatarIcon: function() { - this._setSummaryIcon(this.createNotificationIcon()); - this._notification.update(this._notification.title, null, { customContent: true, icon: this.createNotificationIcon() }); + this._setSummaryIcon(this.createIcon(this.ICON_SIZE)); + this._notification.update(this._notification.title, null, { customContent: true }); }, open: function(notification) { @@ -1031,9 +1031,9 @@ const ApproverSource = new Lang.Class({ this.parent(); }, - createNotificationIcon: function() { + createIcon: function(size) { return new St.Icon({ gicon: this._gicon, - icon_size: this.ICON_SIZE }); + icon_size: size }); } }); diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js index 9a2dd73e7..93b39b98b 100644 --- a/js/ui/windowAttentionHandler.js +++ b/js/ui/windowAttentionHandler.js @@ -73,8 +73,8 @@ const Source = new Lang.Class({ this.signalIDs = []; }, - createNotificationIcon : function() { - return this._app.create_icon_texture(this.ICON_SIZE); + createIcon : function(size) { + return this._app.create_icon_texture(size); }, open : function(notification) { diff --git a/src/shell-util.c b/src/shell-util.c index 869a08958..7dc87d3a4 100644 --- a/src/shell-util.c +++ b/src/shell-util.c @@ -8,6 +8,7 @@ #include "shell-util.h" #include #include +#include #ifdef HAVE__NL_TIME_FIRST_WEEKDAY #include @@ -818,3 +819,33 @@ shell_util_wifexited (int status, return ret; } + +/** + * shell_util_create_pixbuf_from_data: + * @data: (array length=len) (element-type guint8) (transfer full): + * @len: + * @colorspace: + * @has_alpha: + * @bits_per_sample: + * @width: + * @height: + * @rowstride: + * + * Workaround for non-introspectability of gdk_pixbuf_from_data(). + * + * Returns: (transfer full): + */ +GdkPixbuf * +shell_util_create_pixbuf_from_data (const guchar *data, + gsize len, + GdkColorspace colorspace, + gboolean has_alpha, + int bits_per_sample, + int width, + int height, + int rowstride) +{ + return gdk_pixbuf_new_from_data (data, colorspace, has_alpha, + bits_per_sample, width, height, rowstride, + (GdkPixbufDestroyNotify) g_free, NULL); +} diff --git a/src/shell-util.h b/src/shell-util.h index 130b6b995..c1b7c4839 100644 --- a/src/shell-util.h +++ b/src/shell-util.h @@ -6,6 +6,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -44,6 +45,14 @@ gboolean shell_session_is_active_for_systemd (void); gboolean shell_util_wifexited (int status, int *exit); +GdkPixbuf *shell_util_create_pixbuf_from_data (const guchar *data, + gsize len, + GdkColorspace colorspace, + gboolean has_alpha, + int bits_per_sample, + int width, + int height, + int rowstride); G_END_DECLS