[gnome-shell] MessageTray: rework icon handling to split model and view



commit ac6c808124cd8786712ae92739585939e9ec4a64
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Thu Jul 19 15:05:17 2012 +0200

    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 099573b..3e8b52e 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 b50674f..393b305 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.notifications = [];
+        this._source.connect('count-changed', Lang.bind(this, this._updateCount));
+        this._updateCount();
+    },
 
-        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 a02898a..49d1e0b 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 46d7ef0..240969b 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 9a2dd73..93b39b9 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 869a089..7dc87d3 100644
--- a/src/shell-util.c
+++ b/src/shell-util.c
@@ -8,6 +8,7 @@
 #include "shell-util.h"
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 
 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
 #include <langinfo.h>
@@ -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 130b6b9..c1b7c48 100644
--- a/src/shell-util.h
+++ b/src/shell-util.h
@@ -6,6 +6,7 @@
 #include <gio/gio.h>
 #include <clutter/clutter.h>
 #include <libsoup/soup.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 
 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
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]