[gnome-shell] Bug 582248 - Async loading of pixbufs and icon caching



commit d024dbd7795371d75d98c0dc91a1bb93ce0a3c30
Author: Colin Walters <walters verbum org>
Date:   Wed May 13 14:06:24 2009 -0400

    Bug 582248 - Async loading of pixbufs and icon caching
    
    Add a ShellTextureCache class which loads (and can cache)
    pixmap->texture conversions.  This fixes a problem with the
    async code in ClutterTexture that it was lower priority
    than animations, and also ensures we're really only
    loading these pixbufs once in the icon case.
---
 js/ui/Makefile.am         |    1 -
 js/ui/appDisplay.js       |   10 +-
 js/ui/docDisplay.js       |   11 +-
 js/ui/gtkutil.js          |   30 --
 src/Makefile.am           |    2 +
 src/shell-global.c        |  184 -------------
 src/shell-global.h        |    4 -
 src/shell-texture-cache.c |  640 +++++++++++++++++++++++++++++++++++++++++++++
 src/shell-texture-cache.h |   51 ++++
 9 files changed, 702 insertions(+), 231 deletions(-)

diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am
index e344982..29f18a1 100644
--- a/js/ui/Makefile.am
+++ b/js/ui/Makefile.am
@@ -8,7 +8,6 @@ dist_jsui_DATA =		\
 	dnd.js			\
 	docDisplay.js		\
 	genericDisplay.js	\
-        gtkutil.js		\
 	link.js			\
 	main.js			\
 	overlay.js		\
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 28a114b..fd47596 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -10,7 +10,6 @@ const Lang = imports.lang;
 const Signals = imports.signals;
 
 const GenericDisplay = imports.ui.genericDisplay;
-const GtkUtil = imports.ui.gtkutil;
 const Main = imports.ui.main;
 
 const ENTERED_MENU_COLOR = new Clutter.Color();
@@ -70,7 +69,14 @@ AppDisplayItem.prototype = {
         let iconTheme = Gtk.IconTheme.get_default();
 
         let gicon = appInfo.get_icon();
-        let icon = GtkUtil.loadIconToTexture(gicon, GenericDisplay.ITEM_DISPLAY_ICON_SIZE, true);
+        let texCache = Shell.TextureCache.get_default();
+        let icon;
+        if (gicon == null)
+            icon = new Clutter.Texture({ width: GenericDisplay.ITEM_DISPLAY_ICON_SIZE,
+                                         height: GenericDisplay.ITEM_DISPLAY_ICON_SIZE
+                                       });
+        else
+            icon = texCache.load_gicon(gicon, GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
         this._setItemInfo(name, description, icon);
     },
 
diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js
index a1fa17c..cf894de 100644
--- a/js/ui/docDisplay.js
+++ b/js/ui/docDisplay.js
@@ -128,16 +128,7 @@ DocDisplayItem.prototype = {
         if (this._docInfo.get_mime_type() == null || this._docInfo.get_mime_type().indexOf("image/") != 0)
             return null;
 
-        let largePreviewPixbuf = Shell.create_pixbuf_from_image_file(this._docInfo.get_uri(), availableWidth, availableHeight);
-        
-        if (largePreviewPixbuf == null)
-            return null;
-
-        let largePreviewIcon = new Clutter.Texture();
-
-        Shell.clutter_texture_set_from_pixbuf(largePreviewIcon, largePreviewPixbuf); 
-
-        return largePreviewIcon;
+        return Shell.TextureCache.load_uri_sync(this._docInfo.get_uri(), availableWidth, availableHeight);
     }
 };
 
diff --git a/js/ui/gtkutil.js b/js/ui/gtkutil.js
deleted file mode 100644
index 56a9004..0000000
--- a/js/ui/gtkutil.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
-
-const Lang = imports.lang;
-
-const Clutter = imports.gi.Clutter;
-const Gtk = imports.gi.Gtk;
-
-function loadIconToTexture(gicon, size, fallback) {
-    let iconTheme = Gtk.IconTheme.get_default();
-
-    let path = null;
-    let icon = null;
-    if (gicon != null) {
-        let iconinfo = iconTheme.lookup_by_gicon(gicon, size, Gtk.IconLookupFlags.NO_SVG);
-        if (iconinfo)
-            path = iconinfo.get_filename();
-    }
-    if (path) {
-        try {
-            icon = new Clutter.Texture({ width: size, height: size, load_async: true });
-            icon.set_from_file(path);
-            return icon;
-        } catch (e) {
-            icon = null;
-        }
-    }
-    if (icon == null && fallback)
-        icon = new Clutter.Texture({ width: size, height: size });
-    return icon;
-}
\ No newline at end of file
diff --git a/src/Makefile.am b/src/Makefile.am
index f156228..9fd4d65 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -70,6 +70,8 @@ libgnome_shell_la_SOURCES =			\
 	shell-status-menu.h				\
 	shell-tray-manager.c			\
 	shell-tray-manager.h			\
+	shell-texture-cache.c			\
+	shell-texture-cache.h			\
 	shell-wm.c				\
 	shell-wm.h
 
diff --git a/src/shell-global.c b/src/shell-global.c
index cd44571..f7c0500 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -392,190 +392,6 @@ shell_get_thumbnail_for_recent_info(GtkRecentInfo  *recent_info)
     return pixbuf;   
 }
 
-// A private structure for keeping width and height.
-typedef struct {
-  int width;
-  int height;
-} Dimensions;
-
-/**
- * on_image_size_prepared:
- *
- * @pixbuf_loader: #GdkPixbufLoader loading the image
- * @width: the original width of the image
- * @height: the original height of the image
- * @data: pointer to the #Dimensions sructure containing available width and height for the image,
- *        available width or height can be -1 if the dimension is not limited
- *
- * Private function.
- *
- * Sets the size of the image being loaded to fit the available width and height dimensions,
- * but never scales up the image beyond its actual size. 
- * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
- */
-static void
-on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
-                              gint             width,
-                              gint             height,
-                              gpointer         data)
-{
-    Dimensions *available_dimensions = data; 
-    int available_width = available_dimensions->width;
-    int available_height = available_dimensions->height;
-    int scaled_width = -1;
-    int scaled_height = -1; 
-
-    if (width == 0 || height == 0)
-        return;
-
-    if (available_width >= 0 && available_height >= 0) 
-      {
-        // This should keep the aspect ratio of the image intact, because if 
-        // available_width < (available_height * width) / height
-        // than
-        // (available_width * height) / width < available_height
-        // So we are guaranteed to either scale the image to have an available_width 
-        // for width and height scaled accordingly OR have the available_height
-        // for height and width scaled accordingly, whichever scaling results 
-        // in the image that can fit both available dimensions. 
-        scaled_width = MIN(available_width, (available_height * width) / height);
-        scaled_height = MIN(available_height, (available_width * height) / width);
-      } 
-    else if (available_width >= 0) 
-      {
-        scaled_width = available_width;
-        scaled_height = (available_width * height) / width;
-      } 
-    else if (available_height >= 0) 
-      {
-        scaled_width = (available_height * width) / height;
-        scaled_height = available_height;
-      }
-
-    // Scale the image only if that will not increase its original dimensions.
-    if (scaled_width >= 0 && scaled_height >= 0 && scaled_width < width && scaled_height < height) 
-        gdk_pixbuf_loader_set_size (pixbuf_loader, scaled_width, scaled_height);
-}
-
-/**
- * shell_create_pixbuf_from_image_file:
- *
- * @uri: uri of the image file from which to create a pixbuf 
- * @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
- *
- * Return value: (transfer full): #GdkPixbuf with the image file loaded if it was 
- *               generated succesfully, %NULL otherwise
- *               The image is scaled down to fit the available width and height 
- *               dimensions, but the image is never scaled up beyond its actual size.
- *               The pixbuf is rotated according to the associated orientation setting.
- */
-GdkPixbuf *
-shell_create_pixbuf_from_image_file(const char *uri,
-                                    int         available_width,
-                                    int         available_height)
-{
-    GdkPixbufLoader *pixbuf_loader = NULL;
-    GdkPixbuf *pixbuf;
-    GdkPixbuf *rotated_pixbuf = NULL;
-    GFile *file = NULL;  
-    char *contents = NULL;
-    gsize size;
-    GError *error = NULL;
-    gboolean success;
-    Dimensions available_dimensions;
-    int width_before_rotation, width_after_rotation;
-
-    file = g_file_new_for_uri (uri);
-    
-    success = g_file_load_contents (file, NULL, &contents, &size, NULL, &error);
-
-    if (!success)
-      {
-        g_warning ("Could not load contents of the file with uri %s: %s", uri, error->message);
-        goto out;
-      }
-
-    pixbuf_loader = gdk_pixbuf_loader_new ();
-      
-    available_dimensions.width = available_width;
-    available_dimensions.height = available_height;
-    g_signal_connect (pixbuf_loader, "size-prepared",
-                      G_CALLBACK (on_image_size_prepared), &available_dimensions);
-
-    success = gdk_pixbuf_loader_write (pixbuf_loader,
-                                       (const guchar *) contents,
-                                       size, 
-                                       &error);
-    if (!success)
-      {
-        g_warning ("Could not write contents of the file with uri %s to the gdk pixbuf loader: %s", uri, error->message);
-        goto out;
-      }
-
-    success = gdk_pixbuf_loader_close (pixbuf_loader, &error);
-    if (!success)
-      {
-        g_warning ("Could not close the pixbuf loader after writing contents of the file with uri %s: %s", uri, error->message);
-        goto out;
-      } 
-
-    pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
-    width_before_rotation = gdk_pixbuf_get_width (pixbuf);
-
-    rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);  
-    width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
-
-    // There is currently no way to tell if the pixbuf will need to be rotated before it is loaded, 
-    // so we only check that once it is loaded, and reload it again if it needs to be rotated in order
-    // to use the available width and height correctly. 
-    // http://bugzilla.gnome.org/show_bug.cgi?id=579003
-    if (width_before_rotation != width_after_rotation) 
-      {
-        g_object_unref (pixbuf_loader);
-        g_object_unref (rotated_pixbuf);
-        rotated_pixbuf = NULL;
-       
-        pixbuf_loader = gdk_pixbuf_loader_new ();
-      
-        // We know that the image will later be rotated, so we reverse the available dimensions.
-        available_dimensions.width = available_height;
-        available_dimensions.height = available_width;
-        g_signal_connect (pixbuf_loader, "size-prepared",
-                          G_CALLBACK (on_image_size_prepared), &available_dimensions);
-
-        success = gdk_pixbuf_loader_write (pixbuf_loader,
-                                           (const guchar *) contents,
-                                           size, 
-                                           &error);
-        if (!success)
-          {
-            g_warning ("Could not write contents of the file with uri %s to the gdk pixbuf loader: %s", uri, error->message);
-            goto out;
-          } 
-
-       success = gdk_pixbuf_loader_close (pixbuf_loader, &error);
-       if (!success)
-          {
-            g_warning ("Could not close the pixbuf loader after writing contents of the file with uri %s: %s", uri, error->message); 
-            goto out;
-          } 
-    
-        pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
-
-        rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);  
-      }
-
-  out:    
-    g_clear_error (&error);
-    g_free (contents);
-    if (file)
-        g_object_unref (file);
-    if (pixbuf_loader)
-        g_object_unref (pixbuf_loader);
-    
-    return rotated_pixbuf;
-}
 
 /**
  * shell_get_categories_for_desktop_file:
diff --git a/src/shell-global.h b/src/shell-global.h
index a23f377..06c19cc 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -38,10 +38,6 @@ gboolean shell_clutter_texture_set_from_pixbuf (ClutterTexture *texture,
 
 GdkPixbuf *shell_get_thumbnail_for_recent_info(GtkRecentInfo  *recent_info);
 
-GdkPixbuf *shell_create_pixbuf_from_image_file(const char *uri, 
-                                               int available_width, 
-                                               int available_height);
-
 GSList *shell_get_categories_for_desktop_file(const char *desktop_file_name);
 
 guint16 shell_get_event_key_symbol(ClutterEvent *event);
diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c
new file mode 100644
index 0000000..68aaf21
--- /dev/null
+++ b/src/shell-texture-cache.c
@@ -0,0 +1,640 @@
+#include "shell-texture-cache.h"
+#include "shell-global.h"
+#include <gtk/gtk.h>
+
+typedef struct
+{
+  GIcon *icon;
+  guint size;
+} CacheKey;
+
+struct _ShellTextureCachePrivate
+{
+  GHashTable *gicon_cache; /* CacheKey -> CoglTexture* */
+};
+
+static void shell_texture_cache_dispose (GObject *object);
+static void shell_texture_cache_finalize (GObject *object);
+
+G_DEFINE_TYPE(ShellTextureCache, shell_texture_cache, G_TYPE_OBJECT);
+
+static guint
+cache_key_hash (gconstpointer a)
+{
+  CacheKey *akey = (CacheKey *)a;
+
+  if (akey->icon)
+    return g_icon_hash (akey->icon) + 31*akey->size;
+  g_assert_not_reached ();
+}
+
+static gboolean
+cache_key_equal (gconstpointer a,
+                 gconstpointer b)
+{
+  CacheKey *akey = (CacheKey*)a;
+  CacheKey *bkey = (CacheKey*)b;
+
+  if (akey->size != bkey->size)
+    return FALSE;
+  if (akey->icon && bkey->icon)
+    return g_icon_equal (akey->icon, bkey->icon);
+  g_assert_not_reached ();
+}
+
+static void
+cache_key_destroy (gpointer a)
+{
+  CacheKey *akey = (CacheKey*)a;
+  if (akey->icon)
+    g_object_unref (akey->icon);
+  g_free (akey);
+}
+
+static void
+shell_texture_cache_class_init (ShellTextureCacheClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *)klass;
+
+  gobject_class->dispose = shell_texture_cache_dispose;
+  gobject_class->finalize = shell_texture_cache_finalize;
+}
+
+static void
+shell_texture_cache_init (ShellTextureCache *self)
+{
+  self->priv = g_new0 (ShellTextureCachePrivate, 1);
+  self->priv->gicon_cache = g_hash_table_new_full (cache_key_hash, cache_key_equal,
+                                                   cache_key_destroy, cogl_handle_unref);
+}
+
+static void
+shell_texture_cache_dispose (GObject *object)
+{
+  ShellTextureCache *self = (ShellTextureCache*)object;
+
+  if (self->priv->gicon_cache)
+    g_hash_table_destroy (self->priv->gicon_cache);
+  self->priv->gicon_cache = NULL;
+
+  G_OBJECT_CLASS (shell_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+shell_texture_cache_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (shell_texture_cache_parent_class)->finalize (object);
+}
+
+ShellTextureCache*
+shell_texture_cache_new ()
+{
+  return SHELL_TEXTURE_CACHE (g_object_new (SHELL_TYPE_TEXTURE_CACHE,
+				            NULL));
+}
+
+typedef struct {
+  char *uri;
+  GIcon *icon;
+  GtkIconInfo *icon_info;
+  gint width;
+  gint height;
+  gpointer user_data;
+} AsyncIconLookupData;
+
+
+static gboolean
+compute_pixbuf_scale (gint      width,
+                      gint      height,
+                      gint      available_width,
+                      gint      available_height,
+                      gint     *new_width,
+                      gint     *new_height)
+{
+  int scaled_width, scaled_height;
+
+  if (width == 0 || height == 0)
+    return FALSE;
+
+  if (available_width >= 0 && available_height >= 0)
+    {
+      // This should keep the aspect ratio of the image intact, because if
+      // available_width < (available_height * width) / height
+      // than
+      // (available_width * height) / width < available_height
+      // So we are guaranteed to either scale the image to have an available_width
+      // for width and height scaled accordingly OR have the available_height
+      // for height and width scaled accordingly, whichever scaling results
+      // in the image that can fit both available dimensions.
+      scaled_width = MIN (available_width, (available_height * width) / height);
+      scaled_height = MIN (available_height, (available_width * height) / width);
+    }
+  else if (available_width >= 0)
+    {
+      scaled_width = available_width;
+      scaled_height = (available_width * height) / width;
+    }
+  else if (available_height >= 0)
+    {
+      scaled_width = (available_height * width) / height;
+      scaled_height = available_height;
+    }
+  else
+    {
+      scaled_width = scaled_height = 0;
+    }
+
+  // Scale the image only if that will not increase its original dimensions.
+  if (scaled_width >= 0 && scaled_height >= 0 && scaled_width < width && scaled_height < height)
+    {
+      *new_width = scaled_width;
+      *new_height = scaled_height;
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_gicon (GIcon       *icon,
+                        GtkIconInfo *info,
+                        int          size,
+                        GError     **error)
+{
+  int scaled_width, scaled_height;
+  GdkPixbuf *pixbuf = gtk_icon_info_load_icon (info, error);
+  int width, height;
+
+  if (!pixbuf)
+    return NULL;
+
+  width = gdk_pixbuf_get_width (pixbuf);
+  height = gdk_pixbuf_get_height (pixbuf);
+
+  if (compute_pixbuf_scale (width,
+                            height,
+                            size, size,
+                            &scaled_width, &scaled_height))
+    {
+      GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+      g_object_unref (pixbuf);
+      pixbuf = scaled;
+    }
+  return pixbuf;
+}
+
+// A private structure for keeping width and height.
+typedef struct {
+  int width;
+  int height;
+} Dimensions;
+
+/**
+ * on_image_size_prepared:
+ *
+ * @pixbuf_loader: #GdkPixbufLoader loading the image
+ * @width: the original width of the image
+ * @height: the original height of the image
+ * @data: pointer to the #Dimensions sructure containing available width and height for the image,
+ *        available width or height can be -1 if the dimension is not limited
+ *
+ * Private function.
+ *
+ * Sets the size of the image being loaded to fit the available width and height dimensions,
+ * but never scales up the image beyond its actual size.
+ * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
+ */
+static void
+on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
+                        gint             width,
+                        gint             height,
+                        gpointer         data)
+{
+  Dimensions *available_dimensions = data;
+  int available_width = available_dimensions->width;
+  int available_height = available_dimensions->height;
+  int scaled_width;
+  int scaled_height;
+
+  if (compute_pixbuf_scale (width, height, available_width, available_height,
+                            &scaled_width, &scaled_height))
+    gdk_pixbuf_loader_set_size (pixbuf_loader, scaled_width, scaled_height);
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_file (const char     *uri,
+                       int             available_width,
+                       int             available_height,
+                       GError        **error)
+{
+  GdkPixbufLoader *pixbuf_loader = NULL;
+  GdkPixbuf *rotated_pixbuf = NULL;
+  GdkPixbuf *pixbuf;
+  GFile *file = NULL;
+  char *contents = NULL;
+  gsize size;
+  gboolean success;
+  Dimensions available_dimensions;
+  int width_before_rotation, width_after_rotation;
+
+  file = g_file_new_for_uri (uri);
+
+  success = g_file_load_contents (file, NULL, &contents, &size, NULL, error);
+
+  if (!success)
+    {
+      goto out;
+    }
+
+  pixbuf_loader = gdk_pixbuf_loader_new ();
+
+  available_dimensions.width = available_width;
+  available_dimensions.height = available_height;
+  g_signal_connect (pixbuf_loader, "size-prepared",
+                    G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+  success = gdk_pixbuf_loader_write (pixbuf_loader,
+                                     (const guchar *) contents,
+                                     size,
+                                     error);
+  if (!success)
+    goto out;
+  success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+  if (!success)
+    goto out;
+
+  pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+  width_before_rotation = gdk_pixbuf_get_width (pixbuf);
+
+  rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+  width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
+
+  // There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
+  // so we only check that once it is loaded, and reload it again if it needs to be rotated in order
+  // to use the available width and height correctly.
+  // http://bugzilla.gnome.org/show_bug.cgi?id=579003
+  if (width_before_rotation != width_after_rotation)
+    {
+      g_object_unref (pixbuf_loader);
+      g_object_unref (rotated_pixbuf);
+      rotated_pixbuf = NULL;
+
+      pixbuf_loader = gdk_pixbuf_loader_new ();
+
+      // We know that the image will later be rotated, so we reverse the available dimensions.
+      available_dimensions.width = available_height;
+      available_dimensions.height = available_width;
+      g_signal_connect (pixbuf_loader, "size-prepared",
+                        G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+      success = gdk_pixbuf_loader_write (pixbuf_loader,
+                                         (const guchar *) contents,
+                                         size,
+                                         error);
+      if (!success)
+        goto out;
+
+      success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+      if (!success)
+        goto out;
+
+      pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+      rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+    }
+
+out:
+  g_free (contents);
+  if (file)
+    g_object_unref (file);
+  if (pixbuf_loader)
+    g_object_unref (pixbuf_loader);
+  return rotated_pixbuf;
+}
+
+static void
+load_pixbuf_thread (GSimpleAsyncResult *result,
+                    GObject *object,
+                    GCancellable *cancellable)
+{
+  GdkPixbuf *pixbuf;
+  AsyncIconLookupData *data;
+  GError *error = NULL;
+
+  data = g_simple_async_result_get_op_res_gpointer (result);
+
+  if (data->uri)
+    pixbuf = impl_load_pixbuf_file (data->uri, data->width, data->height, &error);
+  else if (data->icon)
+    pixbuf = impl_load_pixbuf_gicon (data->icon, data->icon_info, data->width, &error);
+  else
+    g_assert_not_reached ();
+
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (result, error);
+      return;
+    }
+
+
+  g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf),
+                                             g_object_unref);
+}
+
+static void
+icon_lookup_data_destroy (gpointer p)
+{
+  AsyncIconLookupData *data = p;
+
+  if (data->icon)
+    {
+      g_object_unref (data->icon);
+      gtk_icon_info_free (data->icon_info);
+    }
+  else if (data->uri)
+    g_free (data->uri);
+
+  g_free (data);
+}
+
+/**
+ * load_icon_pixbuf_async:
+ *
+ * Asynchronously load the #GdkPixbuf associated with a #GIcon.  Currently
+ * the #GtkIconInfo must have already been provided.
+ */
+static void
+load_icon_pixbuf_async (ShellTextureCache    *cache,
+                        GIcon                *icon,
+                        GtkIconInfo          *icon_info,
+                        gint                  size,
+                        GCancellable         *cancellable,
+                        GAsyncReadyCallback   callback,
+                        gpointer              user_data)
+{
+  GSimpleAsyncResult *result;
+  AsyncIconLookupData *data;
+
+  data = g_new0 (AsyncIconLookupData, 1);
+  data->icon = g_object_ref (icon);
+  data->icon_info = gtk_icon_info_copy (icon_info);
+  data->width = data->height = size;
+  data->user_data = user_data;
+
+  result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_icon_pixbuf_async);
+
+  g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy);
+  g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
+}
+
+static void
+load_uri_pixbuf_async (ShellTextureCache *cache,
+                       const char *uri,
+                       guint width,
+                       guint height,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
+{
+  GSimpleAsyncResult *result;
+  AsyncIconLookupData *data;
+
+  data = g_new0 (AsyncIconLookupData, 1);
+  data->uri = g_strdup (uri);
+  data->width = width;
+  data->height = height;
+  data->user_data = user_data;
+
+  result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_uri_pixbuf_async);
+
+  g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy);
+  g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
+}
+
+static GdkPixbuf *
+load_pixbuf_async_finish (ShellTextureCache *cache, GAsyncResult *result, GError **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+  if (g_simple_async_result_propagate_error (simple, error))
+    return NULL;
+  return g_simple_async_result_get_op_res_gpointer (simple);
+}
+
+typedef struct {
+  char *uri;
+  GIcon *icon;
+  GtkIconInfo *icon_info;
+  guint width;
+  guint height;
+  ClutterTexture *texture;
+} AsyncTextureLoadData;
+
+static CoglHandle
+pixbuf_to_cogl_handle (GdkPixbuf *pixbuf)
+{
+  return cogl_texture_new_from_data (gdk_pixbuf_get_width (pixbuf),
+                                     gdk_pixbuf_get_height (pixbuf),
+                                     63, /* taken from clutter-texture.c default */
+                                     COGL_TEXTURE_AUTO_MIPMAP,
+                                     gdk_pixbuf_get_has_alpha (pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+                                     COGL_PIXEL_FORMAT_ANY,
+                                     gdk_pixbuf_get_rowstride (pixbuf),
+                                     gdk_pixbuf_get_pixels (pixbuf));
+}
+
+static void
+on_pixbuf_loaded (GObject      *source,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  ShellTextureCache *cache;
+  AsyncTextureLoadData *data;
+  GdkPixbuf *pixbuf;
+  GError *error = NULL;
+  CoglHandle texdata;
+  CacheKey *key;
+
+  data = user_data;
+  cache = SHELL_TEXTURE_CACHE (source);
+  pixbuf = load_pixbuf_async_finish (cache, result, &error);
+  if (pixbuf == NULL)
+    {
+      /* TODO - we need a "broken image" display of some sort */
+      goto out;
+    }
+
+  texdata = pixbuf_to_cogl_handle (pixbuf);
+
+  if (data->icon)
+    {
+      gpointer orig_key, value;
+
+      key = g_new0 (CacheKey, 1);
+      key->icon = g_object_ref (data->icon);
+      key->size = data->width;
+
+      if (!g_hash_table_lookup_extended (cache->priv->gicon_cache, key,
+                                         &orig_key, &value))
+        g_hash_table_insert (cache->priv->gicon_cache, key,
+                             texdata);
+      else
+        cache_key_destroy (key);
+    }
+
+  clutter_texture_set_cogl_texture (data->texture, texdata);
+
+out:
+  if (data->icon)
+    {
+      gtk_icon_info_free (data->icon_info);
+      g_object_unref (data->icon);
+    }
+  else if (data->uri)
+    g_free (data->uri);
+  /* Alternatively we could weakref and just do nothing if the texture
+     is destroyed */
+  g_object_unref (data->texture);
+
+  g_free (data);
+}
+
+/**
+ * shell_texture_cache_load_gicon:
+ *
+ * This method returns a new #ClutterClone for a given #GIcon.  If the
+ * icon isn't loaded already, the texture will be filled asynchronously.
+ *
+ * Return Value: (transfer none): A new #ClutterActor for the icon
+ */
+ClutterActor *
+shell_texture_cache_load_gicon (ShellTextureCache *cache,
+                                GIcon             *icon,
+                                gint               size)
+{
+  ClutterTexture *texture;
+  CoglHandle texdata;
+  CacheKey key;
+
+  texture = CLUTTER_TEXTURE (clutter_texture_new ());
+  clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
+
+  key.icon = icon;
+  key.size = size;
+  texdata = g_hash_table_lookup (cache->priv->gicon_cache, &key);
+
+  if (texdata == NULL)
+    {
+      GtkIconTheme *theme;
+      GtkIconInfo *info;
+
+      /* Do theme lookups in the main thread to avoid thread-unsafety */
+      theme = gtk_icon_theme_get_default ();
+
+      info = gtk_icon_theme_lookup_by_gicon (theme, icon, size, GTK_ICON_LOOKUP_USE_BUILTIN);
+      if (info != NULL)
+        {
+          AsyncTextureLoadData *data;
+          data = g_new0 (AsyncTextureLoadData, 1);
+
+          data->icon = icon;
+          data->icon_info = info;
+          data->texture = g_object_ref (texture);
+          load_icon_pixbuf_async (cache, icon, info, size, NULL, on_pixbuf_loaded, data);
+        }
+    }
+  else
+    {
+      clutter_texture_set_cogl_texture (texture, texdata);
+    }
+
+  return CLUTTER_ACTOR (texture);
+}
+
+/**
+ * shell_texture_cache_load_uri:
+ *
+ * @cache: The texture cache instance
+ * @uri: uri of the image file from which to create a pixbuf
+ * @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
+ *
+ * Asynchronously load an image.   Initially, the returned texture will have a natural
+ * size of zero.  At some later point, either the image will be loaded successfully
+ * and at that point size will be negotiated, or upon an error, no image will be set.
+ *
+ * Return value: (transfer none): A new #ClutterActor with no image loaded initially.
+ */
+ClutterActor *
+shell_texture_cache_load_uri_async (ShellTextureCache *cache,
+                                    const gchar *uri,
+                                    int available_width,
+                                    int available_height)
+{
+  ClutterTexture *texture;
+  AsyncTextureLoadData *data;
+
+  texture = CLUTTER_TEXTURE (clutter_texture_new ());
+
+  data = g_new0 (AsyncTextureLoadData, 1);
+  data->uri = g_strdup (uri);
+  data->width = available_width;
+  data->height = available_height;
+  data->texture = g_object_ref (texture);
+  load_uri_pixbuf_async (cache, uri, available_width, available_height, NULL, on_pixbuf_loaded, data);
+
+  return CLUTTER_ACTOR (texture);
+}
+
+/**
+ * shell_texture_cache_load_uri_sync:
+ *
+ * @cache: The texture cache instance
+ * @uri: uri of the image file from which to create a pixbuf
+ * @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
+ * @error: Return location for error
+ *
+ * Synchronously load an image from a uri.  The image is scaled down to fit the
+ * available width and height imensions, but the image is never scaled up beyond
+ * its actual size. The pixbuf is rotated according to the associated orientation
+ * setting.
+ *
+ * Return value: (transfer none): A new #ClutterActor with the image file loaded if it was
+ *               generated succesfully, %NULL otherwise
+ */
+ClutterActor *
+shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
+                                   const gchar       *uri,
+                                   int                available_width,
+                                   int                available_height,
+                                   GError            **error)
+{
+  ClutterTexture *texture;
+  GdkPixbuf *pixbuf;
+  CoglHandle texdata;
+
+  pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error);
+  if (!pixbuf)
+    return NULL;
+
+  texture = CLUTTER_TEXTURE (clutter_texture_new ());
+  texdata = pixbuf_to_cogl_handle (pixbuf);
+  clutter_texture_set_cogl_texture (texture, texdata);
+
+  return CLUTTER_ACTOR (texture);
+}
+
+static ShellTextureCache *instance = NULL;
+
+/**
+ * shell_texture_cache_get_default:
+ *
+ * Return value: (transfer none): The global texture cache
+ */
+ShellTextureCache*
+shell_texture_cache_get_default ()
+{
+  if (instance == NULL)
+    instance = g_object_new (SHELL_TYPE_TEXTURE_CACHE, NULL);
+  return instance;
+}
diff --git a/src/shell-texture-cache.h b/src/shell-texture-cache.h
new file mode 100644
index 0000000..68dd7d6
--- /dev/null
+++ b/src/shell-texture-cache.h
@@ -0,0 +1,51 @@
+#ifndef __SHELL_TEXTURE_CACHE_H__
+#define __SHELL_TEXTURE_CACHE_H__
+
+#include <gio/gio.h>
+#include <clutter/clutter.h>
+
+#define SHELL_TYPE_TEXTURE_CACHE                 (shell_texture_cache_get_type ())
+#define SHELL_TEXTURE_CACHE(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCache))
+#define SHELL_TEXTURE_CACHE_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCacheClass))
+#define SHELL_IS_TEXTURE_CACHE(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_TEXTURE_CACHE))
+#define SHELL_IS_TEXTURE_CACHE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_TEXTURE_CACHE))
+#define SHELL_TEXTURE_CACHE_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCacheClass))
+
+typedef struct _ShellTextureCache ShellTextureCache;
+typedef struct _ShellTextureCacheClass ShellTextureCacheClass;
+
+typedef struct _ShellTextureCachePrivate ShellTextureCachePrivate;
+
+struct _ShellTextureCache
+{
+  GObject parent;
+
+  ShellTextureCachePrivate *priv;
+};
+
+struct _ShellTextureCacheClass
+{
+  GObjectClass parent_class;
+
+};
+
+GType shell_texture_cache_get_type (void) G_GNUC_CONST;
+
+ShellTextureCache* shell_texture_cache_get_default();
+
+ClutterActor *shell_texture_cache_load_gicon (ShellTextureCache *cache,
+                                              GIcon             *icon,
+                                              gint               size);
+
+ClutterActor *shell_texture_cache_load_uri_async (ShellTextureCache *cache,
+                                                  const gchar       *filename,
+                                                  int                available_width,
+                                                  int                available_height);
+
+ClutterActor *shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
+                                                 const gchar       *filename,
+                                                 int                available_width,
+                                                 int                available_height,
+                                                 GError           **error);
+
+#endif /* __SHELL_TEXTURE_CACHE_H__ */



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