diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am index e344982ae..29f18a10a 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 28a114b36..fd4759669 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 a1fa17cc8..cf894de16 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 56a900468..000000000 --- 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 f15622852..9fd4d659c 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 cd445714a..f7c0500d7 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 a23f37735..06c19cc6e 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 000000000..68aaf214a --- /dev/null +++ b/src/shell-texture-cache.c @@ -0,0 +1,640 @@ +#include "shell-texture-cache.h" +#include "shell-global.h" +#include + +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 000000000..68dd7d6b8 --- /dev/null +++ b/src/shell-texture-cache.h @@ -0,0 +1,51 @@ +#ifndef __SHELL_TEXTURE_CACHE_H__ +#define __SHELL_TEXTURE_CACHE_H__ + +#include +#include + +#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__ */