[gnome-shell/message-tray] Add a message tray with icons for ongoing conversations



commit 3b5c468cbf1caad78003c0516118b4f0d969fc8e
Author: Marina Zhurakhinskaya <marinaz redhat com>
Date:   Wed Dec 2 16:34:48 2009 -0500

    Add a message tray with icons for ongoing conversations
    
    Add a message tray that slides out when you move your mouse to
    the bottom of the screen. The icons for ongoing conversations
    are added to the message tray when the first message in the
    conversation is received. The icon is removed when the corresponding
    conversation window is closed.
    
    Store the avatar icons in the texture cache and use the checksum for
    the data bytes for the icon as the key. This allows to reuse the icon
    data for the message tray icon.
    
    Add st_box_layout_insert_actor() that allows inserting an actor at the
    arbitrary position in the container. It is needed to be able to add the
    icon representing the most recent conversation to the front of the list
    of icons in the message tray.

 data/theme/gnome-shell.css |   14 ++++++-
 js/ui/main.js              |    2 +
 js/ui/messageTray.js       |   99 ++++++++++++++++++++++++++++++++++++++++++++
 js/ui/messaging.js         |   20 +++++++--
 src/shell-texture-cache.c  |   55 +++++++++++++++++++-----
 src/shell-texture-cache.h  |    3 +-
 src/st/st-box-layout.c     |   33 +++++++++++++++
 src/st/st-box-layout.h     |    8 ++++
 8 files changed, 215 insertions(+), 19 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index b04cb79..922a7b4 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -317,7 +317,19 @@ StTooltip {
     background: rgba(0,0,0,0.9);
     border: 1px solid rgba(128,128,128,0.45);
     color: white;
-    padding: 10px;
+    padding: 4px;
+    spacing: 10px;
+}
+
+#message-tray {
+    border-radius: 5px;
+    background: rgba(0,0,0,0.9);
+    border: 1px solid rgba(128,128,128,0.45);
+    padding: 4px;
+    height: 40px;
+}
+
+#message-tray-inner {
     spacing: 10px;
 }
 
diff --git a/js/ui/main.js b/js/ui/main.js
index 0bea5d5..10ea3a5 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -40,6 +40,7 @@ let wm = null;
 let messaging = null;
 let notificationDaemon = null;
 let notificationPopup = null;
+let messageTray = null;
 let recorder = null;
 let shellDBusService = null;
 let modalCount = 0;
@@ -113,6 +114,7 @@ function start() {
     messaging = new Messaging.Messaging();
     notificationDaemon = new NotificationDaemon.NotificationDaemon();
     notificationPopup = new MessageTray.Notification();
+    messageTray = new MessageTray.MessageTray();
 
     global.screen.connect('toggle-recording', function() {
         if (recorder == null) {
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 512aec0..d105c98 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -10,6 +10,8 @@ const Main = imports.ui.main;
 const ANIMATION_TIME = 0.2;
 const NOTIFICATION_TIMEOUT = 4;
 
+const MESSAGE_TRAY_TIMEOUT = 0.2;
+
 function Notification() {
     this._init();
 }
@@ -80,3 +82,100 @@ Notification.prototype = {
             this.actor.hide();
     }
 };
+
+function MessageTray() {
+    this._init();
+}
+
+MessageTray.prototype = {
+    _init: function() {
+        this.actor = new St.Bin({ name: 'message-tray',
+                                  reactive: true,
+                                  x_align: St.Align.END });
+        // Directly adding the actor to Main.chrome.actor is a hack to
+        // work around the fact that there is no way to add an actor that
+        // affects the input region but not the shape.
+        // See: https://bugzilla.gnome.org/show_bug.cgi?id=597044
+        Main.chrome.actor.add_actor(this.actor);
+        Main.chrome.addInputRegionActor(this.actor);
+
+        let primary = global.get_primary_monitor();
+        this.actor.x = 0;
+        this.actor.y = primary.height - 1;
+
+        this.actor.width = primary.width;
+
+        this.actor.connect('enter-event',
+                           Lang.bind(this, this._onMessageTrayEntered));
+        this.actor.connect('leave-event',
+                           Lang.bind(this, this._onMessageTrayLeft));
+        this._isShowing = false;
+        this.actor.show();
+
+        this._tray = new St.BoxLayout({ name: 'message-tray-inner' });
+        this.actor.child = this._tray;
+        this._tray.expand = true;
+        this._icons = {};
+    },
+
+    contains: function(id) {
+        return this._icons.hasOwnProperty(id);
+    },
+
+    add: function(id, icon) {
+        if (this.contains(id))
+            return;
+
+        let iconBox = new St.Bin();
+        iconBox.child = icon;
+        this._tray.insert_actor(iconBox, 0);
+        this._icons[id] = iconBox;
+    },
+
+    remove: function(id) {
+        if (!this.contains(id))
+            return;
+
+        this._tray.remove_actor(this._icons[id]);
+        this._icons[id].destroy();
+        delete this._icons[id];
+    },
+
+    _onMessageTrayEntered: function() {
+        if (this._hideTimeoutId > 0)
+            Mainloop.source_remove(this._hideTimeoutId);
+
+        if (this._isShowing)
+            return;
+
+        this._isShowing = true;
+        let primary = global.get_primary_monitor();
+        Tweener.addTween(this.actor,
+                         { y: primary.height - this.actor.height,
+                           time: ANIMATION_TIME,
+                           transition: "easeOutQuad"
+                         });
+    },
+
+    _onMessageTrayLeft: function() {
+        if (!this._isShowing)
+            return;
+
+        this._hideTimeoutId = Mainloop.timeout_add(MESSAGE_TRAY_TIMEOUT * 1000, Lang.bind(this, this._hide));
+    },
+
+    _hide: function() {
+        this._isShowing = false;
+        this._hideTimeoutId = 0;
+
+        let primary = global.get_primary_monitor();
+
+        Tweener.addTween(this.actor,
+                         { y: primary.height - 1,
+                           time: ANIMATION_TIME,
+                           transition: "easeOutQuad"
+                         });
+        return false;
+    }
+};
+
diff --git a/js/ui/messaging.js b/js/ui/messaging.js
index 8113f87..2e54cbb 100644
--- a/js/ui/messaging.js
+++ b/js/ui/messaging.js
@@ -289,10 +289,13 @@ Source.prototype = {
         this._closedId = this._channel.connect('Closed', Lang.bind(this, this._channelClosed));
 
         this._targetId = targetId;
-        log('channel for ' + this._targetId);
+        log('channel for ' + this._targetId + ' channelPath ' + channelPath);
 
         this._pendingMessages = null;
 
+        this._avatar = null;
+        this._avatarBytes = null;
+
         // FIXME: RequestAvatar is deprecated in favor of
         // RequestAvatars; but RequestAvatars provides no explicit
         // indication of "no avatar available", so there's no way we
@@ -316,8 +319,8 @@ Source.prototype = {
 
     _gotAvatar: function(result, excp) {
         if (result) {
-            let bytes = result[0];
-            this._avatar = Shell.TextureCache.get_default().load_from_data(bytes, bytes.length, AVATAR_SIZE, AVATAR_SIZE);
+            this._avatarBytes = result[0];
+            this._avatar = Shell.TextureCache.get_default().load_from_data(this._avatarBytes, this._avatarBytes.length, AVATAR_SIZE);
             log('got avatar for ' + this._targetId);
         } else {
             // fallback avatar (FIXME)
@@ -338,14 +341,23 @@ Source.prototype = {
     },
 
     _channelClosed: function() {
-        log('closed');
+        log('Channel closed ' + this._targetId);
         this._channel.disconnect(this._closedId);
         this._channelText.disconnect(this._receivedId);
+        Main.messageTray.remove(this._targetId);
     },
 
     _receivedMessage: function(channel, id, timestamp, sender,
                                type, flags, text) {
         log('Received: id ' + id + ', time ' + timestamp + ', sender ' + sender + ', type ' + type + ', flags ' + flags + ': ' + text);
         Main.notificationPopup.show(this._avatar, text);
+        if (!Main.messageTray.contains(this._targetId)) {
+            let avatarForMessageTray = null;
+            if (this._avatarBytes)
+                avatarForMessageTray = Shell.TextureCache.get_default().load_from_data(this._avatarBytes, this._avatarBytes.length, AVATAR_SIZE);
+            else
+                avatarForMessageTray = Shell.TextureCache.get_default().load_icon_name("stock_person", AVATAR_SIZE);
+            Main.messageTray.add(this._targetId, avatarForMessageTray);
+        }
     }
 };
diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c
index 2bea038..bbbec88 100644
--- a/src/shell-texture-cache.c
+++ b/src/shell-texture-cache.c
@@ -6,6 +6,7 @@
 #define GNOME_DESKTOP_USE_UNSTABLE_API
 #include <libgnomeui/gnome-desktop-thumbnail.h>
 #include <string.h>
+#include <glib.h>
 
 typedef struct
 {
@@ -15,6 +16,7 @@ typedef struct
   GIcon *icon;
   gchar *uri;
   gchar *thumbnail_uri;
+  gchar *checksum;
 
   /* This one is common to all */
   guint size;
@@ -49,6 +51,8 @@ cache_key_hash (gconstpointer a)
     base_hash = g_str_hash (akey->uri);
   else if (akey->thumbnail_uri)
     base_hash = g_str_hash (akey->thumbnail_uri);
+  else if (akey->checksum)
+    base_hash = g_str_hash (akey->checksum);
   else
     g_assert_not_reached ();
   return base_hash + 31*akey->size;
@@ -74,6 +78,8 @@ cache_key_equal (gconstpointer a,
     return strcmp (akey->uri, bkey->uri) == 0;
   else if (akey->thumbnail_uri && bkey->thumbnail_uri)
     return strcmp (akey->thumbnail_uri, bkey->thumbnail_uri) == 0;
+  else if (akey->checksum && bkey->checksum)
+    return strcmp (akey->checksum, bkey->checksum) == 0;
 
   return FALSE;
 }
@@ -87,6 +93,7 @@ cache_key_dup (CacheKey *key)
     ret->icon = g_object_ref (key->icon);
   ret->uri = g_strdup (key->uri);
   ret->thumbnail_uri = g_strdup (key->thumbnail_uri);
+  ret->checksum = g_strdup (key->checksum);
   ret->size = key->size;
   return ret;
 }
@@ -99,6 +106,7 @@ cache_key_destroy (gpointer a)
     g_object_unref (akey->icon);
   g_free (akey->uri);
   g_free (akey->thumbnail_uri);
+  g_free (akey->checksum);
   g_free (akey);
 }
 
@@ -658,6 +666,7 @@ typedef struct {
   gboolean thumbnail;
   char *mimetype;
   GtkRecentInfo *recent_info;
+  char *checksum;
   GIcon *icon;
   GtkIconInfo *icon_info;
   guint width;
@@ -1145,8 +1154,7 @@ shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
  * @cache: The texture cache instance
  * @data: Raw image data
  * @len: length of @data
- * @available_width: available width for the image, can be -1 if not limited
- * @available_height: available height for the image, can be -1 if not limited
+ * @size: Size in pixels to use for the resulting texture
  * @error: Return location for error
  *
  * Synchronously creates an image from @data. The image is scaled down
@@ -1161,24 +1169,47 @@ ClutterActor *
 shell_texture_cache_load_from_data (ShellTextureCache *cache,
                                     const guchar      *data,
                                     gsize              len,
-                                    int                available_width,
-                                    int                available_height,
+                                    int                size,
                                     GError           **error)
 {
   ClutterTexture *texture;
   CoglHandle texdata;
   GdkPixbuf *pixbuf;
-
-  pixbuf = impl_load_pixbuf_data (data, len, available_width, available_height, error);
-  if (pixbuf == NULL)
-    return NULL;
+  CacheKey key;
+  gchar *checksum;
 
   texture = create_default_texture (cache);
-  texdata = pixbuf_to_cogl_handle (pixbuf);
-  set_texture_cogl_texture (texture, texdata);
+  clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
 
-  g_object_unref (pixbuf);
-  cogl_handle_unref (texdata);
+  checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, data, len);
+
+  memset (&key, 0, sizeof(key));
+  key.size = size;
+  key.checksum = checksum;
+
+  texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key);
+  if (texdata == NULL)
+    {
+      g_debug ("creating new pixbuf");
+      pixbuf = impl_load_pixbuf_data (data, len, size, size, error);
+      if (!pixbuf)
+        {
+          g_object_unref (texture);
+          return NULL;
+        }
+
+      texdata = pixbuf_to_cogl_handle (pixbuf);
+      g_object_unref (pixbuf);
+
+      set_texture_cogl_texture (texture, texdata);
+
+      g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata);
+    }
+  else
+    {
+      g_debug ("using texture from cache");
+      set_texture_cogl_texture (texture, texdata);
+    }
 
   return CLUTTER_ACTOR (texture);
 }
diff --git a/src/shell-texture-cache.h b/src/shell-texture-cache.h
index d88967a..14d132e 100644
--- a/src/shell-texture-cache.h
+++ b/src/shell-texture-cache.h
@@ -83,8 +83,7 @@ ClutterActor *shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
 ClutterActor *shell_texture_cache_load_from_data (ShellTextureCache *cache,
                                                   const guchar      *data,
                                                   gsize              len,
-                                                  int                available_width,
-                                                  int                available_height,
+                                                  int                size,
                                                   GError           **error);
 
 gboolean shell_texture_cache_pixbuf_equal (ShellTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b);
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
index 88c2d93..d23226a 100644
--- a/src/st/st-box-layout.c
+++ b/src/st/st-box-layout.c
@@ -1418,3 +1418,36 @@ st_box_layout_get_n_children  (StBoxLayout *self)
 {
   return g_list_length (self->priv->children);
 }
+
+void
+st_box_layout_move_child (StBoxLayout  *self,
+                          ClutterActor *actor,
+                          int           pos)
+{
+  StBoxLayoutPrivate *priv = ST_BOX_LAYOUT (self)->priv;
+
+  GList *item = NULL;
+
+  item = g_list_find (priv->children, actor);
+
+  if (item == NULL)
+    {
+      g_warning ("Actor of type '%s' is not a child of the StBoxLayout container",
+                 g_type_name (G_OBJECT_TYPE (actor)));
+      return;
+    }
+
+  priv->children = g_list_delete_link (priv->children, item);
+  priv->children = g_list_insert (priv->children, actor, pos);
+  clutter_actor_queue_relayout ((ClutterActor*) self);
+}
+
+void
+st_box_layout_insert_actor (StBoxLayout  *self,
+                            ClutterActor *actor,
+                            int           pos)
+{
+  clutter_container_add_actor((ClutterContainer*) self, actor);
+  st_box_layout_move_child(self, actor, pos);
+}
+
diff --git a/src/st/st-box-layout.h b/src/st/st-box-layout.h
index 21cd0b7..33b5924 100644
--- a/src/st/st-box-layout.h
+++ b/src/st/st-box-layout.h
@@ -95,6 +95,14 @@ void     st_box_layout_destroy_children (StBoxLayout *box);
 
 guint    st_box_layout_get_n_children  (StBoxLayout *box);
 
+void     st_box_layout_move_child (StBoxLayout  *self,
+                                   ClutterActor *actor,
+                                   int           pos);
+
+void     st_box_layout_insert_actor (StBoxLayout  *self,
+                                     ClutterActor *actor,
+                                     int           pos);
+
 G_END_DECLS
 
 #endif /* _ST_BOX_LAYOUT_H */



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