From 22948b3d396ca843a7517c28437cd8a8785417af Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 9 Feb 2010 12:42:07 -0500 Subject: [PATCH] Merge St.TextureCache and Shell.TextureCache Brute force merge these two by essentially replacing St.TextureCache with a (renamed) Shell.TextureCache. One function was added for convenience, namely "st_texture_cache_load_file_simple". St.TextureCache had a function to load a texture from a filename, and it returned NULL on error but only half the callers actually checked this. This function is better. https://bugzilla.gnome.org/show_bug.cgi?id=607500 --- configure.ac | 4 +- js/misc/docInfo.js | 5 +- js/ui/appDisplay.js | 2 +- js/ui/dash.js | 8 +- js/ui/docDisplay.js | 4 +- js/ui/lookingGlass.js | 4 +- js/ui/notificationDaemon.js | 2 +- js/ui/placeDisplay.js | 12 +- js/ui/runDialog.js | 2 +- js/ui/statusMenu.js | 2 +- src/Makefile.am | 2 - src/shell-app-system.c | 10 +- src/shell-doc-system.c | 1 - src/shell-texture-cache.c | 1499 --------------------------- src/shell-texture-cache.h | 100 -- src/shell-window-tracker.c | 6 +- src/st/st-entry.c | 4 +- src/st/st-texture-cache.c | 1895 +++++++++++++++++++++++++++-------- src/st/st-texture-cache.h | 159 +-- src/st/st-widget.c | 16 +- 20 files changed, 1607 insertions(+), 2130 deletions(-) delete mode 100644 src/shell-texture-cache.c delete mode 100644 src/shell-texture-cache.h diff --git a/configure.ac b/configure.ac index 7d9de857d..315bdfd89 100644 --- a/configure.ac +++ b/configure.ac @@ -59,10 +59,10 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugins >= 2.29.0 gjs-gi-1.0 libgnome-menu $recorder_modules gconf-2.0 gdk-x11-2.0 clutter-x11-1.0 clutter-glx-1.0 - gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0 + libstartup-notification-1.0 gobject-introspection-1.0 >= 0.6.5) PKG_CHECK_MODULES(TIDY, clutter-1.0) -PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libcroco-0.6) +PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libcroco-0.6 gnome-desktop-2.0 >= 2.26) PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0) PKG_CHECK_MODULES(TRAY, gtk+-2.0) diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js index 559e9ecef..205e619d7 100644 --- a/js/misc/docInfo.js +++ b/js/misc/docInfo.js @@ -3,8 +3,9 @@ const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const Gtk = imports.gi.Gtk; -const Shell = imports.gi.Shell; +const St = imports.gi.St; +const Shell = imports.gi.Shell; const Lang = imports.lang; const Signals = imports.signals; const Search = imports.ui.search; @@ -30,7 +31,7 @@ DocInfo.prototype = { }, createIcon : function(size) { - return Shell.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo); + return St.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo); }, launch : function() { diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index fdb2387ae..f18745f6c 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -694,7 +694,7 @@ AppIconMenu.prototype = { this._windowContainer.show(); let iconsDiffer = false; - let texCache = Shell.TextureCache.get_default(); + let texCache = St.TextureCache.get_default(); if (windows.length > 0) { let firstIcon = windows[0].mini_icon; for (let i = 1; i < windows.length; i++) { diff --git a/js/ui/dash.js b/js/ui/dash.js index 4082bcef7..041c2a11c 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -268,11 +268,11 @@ SearchEntry.prototype = { box.append(this._iconBox, Big.BoxPackFlags.END); let magnifierUri = "file://" + global.imagedir + "magnifier.svg"; - this._magnifierIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER, - magnifierUri, 18, 18); + this._magnifierIcon = St.TextureCache.get_default().load_uri_sync(St.TextureCachePolicy.FOREVER, + magnifierUri, 18, 18); let closeUri = "file://" + global.imagedir + "close-black.svg"; - this._closeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER, - closeUri, 18, 18); + this._closeIcon = St.TextureCache.get_default().load_uri_sync(St.TextureCachePolicy.FOREVER, + closeUri, 18, 18); this._closeIcon.reactive = true; this._closeIcon.connect('button-press-event', Lang.bind(this, function () { // Resetting this.entry.text will trigger notify::text signal which will diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index 1e0e97abe..b2d97fc94 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -85,8 +85,8 @@ DocDisplayItem.prototype = { return null; try { - return Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.NONE, - this._docInfo.uri, -1, -1); + return St.TextureCache.get_default().load_uri_sync(St.TextureCachePolicy.NONE, + this._docInfo.uri, -1, -1); } catch (e) { // An exception will be raised when the image format isn't know /* FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=591480: should diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 403324b5f..08d191bfd 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -484,8 +484,8 @@ LookingGlass.prototype = { let toolbar = new St.BoxLayout({ name: "Toolbar" }); this.actor.add_actor(toolbar); - let inspectIcon = Shell.TextureCache.get_default().load_gicon(new Gio.ThemedIcon({ name: 'gtk-color-picker' }), - 24); + let inspectIcon = St.TextureCache.get_default().load_gicon(new Gio.ThemedIcon({ name: 'gtk-color-picker' }), + 24); toolbar.add_actor(inspectIcon); inspectIcon.reactive = true; inspectIcon.connect('button-press-event', Lang.bind(this, function () { diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 2977adcb9..0183962af 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -318,7 +318,7 @@ Source.prototype = { }, createIcon: function(size) { - let textureCache = Shell.TextureCache.get_default(); + let textureCache = St.TextureCache.get_default(); if (this._icon) { if (this._icon.substr(0, 7) == 'file://') diff --git a/js/ui/placeDisplay.js b/js/ui/placeDisplay.js index 666ba6067..da7fe0356 100644 --- a/js/ui/placeDisplay.js +++ b/js/ui/placeDisplay.js @@ -77,7 +77,7 @@ PlaceDeviceInfo.prototype = { iconFactory: function(size) { let icon = this._mount.get_icon(); - return Shell.TextureCache.get_default().load_gicon(icon, size); + return St.TextureCache.get_default().load_gicon(icon, size); }, launch: function() { @@ -122,7 +122,7 @@ PlacesManager.prototype = { let homeIcon = Shell.util_get_icon_for_uri (homeUri); this._home = new PlaceInfo('special:home', homeLabel, function(size) { - return Shell.TextureCache.get_default().load_gicon(homeIcon, size); + return St.TextureCache.get_default().load_gicon(homeIcon, size); }, function() { Gio.app_info_launch_default_for_uri(homeUri, global.create_app_launch_context()); @@ -135,7 +135,7 @@ PlacesManager.prototype = { let desktopIcon = Shell.util_get_icon_for_uri (desktopUri); this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel, function(size) { - return Shell.TextureCache.get_default().load_gicon(desktopIcon, size); + return St.TextureCache.get_default().load_gicon(desktopIcon, size); }, function() { Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context()); @@ -143,7 +143,7 @@ PlacesManager.prototype = { this._connect = new PlaceInfo('special:connect', _("Connect to..."), function (size) { - return Shell.TextureCache.get_default().load_icon_name("applications-internet", size); + return St.TextureCache.get_default().load_icon_name("applications-internet", size); }, function () { new Shell.Process({ args: ['nautilus-connect-server'] }).run(); @@ -310,7 +310,7 @@ PlacesManager.prototype = { let item = new PlaceInfo('bookmark:' + bookmark, label, function(size) { - return Shell.TextureCache.get_default().load_gicon(icon, size); + return St.TextureCache.get_default().load_gicon(icon, size); }, function() { Gio.app_info_launch_default_for_uri(bookmark, global.create_app_launch_context()); @@ -414,7 +414,7 @@ DashPlaceDisplayItem.prototype = { this.actor.append(text, Big.BoxPackFlags.EXPAND); if (info.isRemovable()) { - let removeIcon = Shell.TextureCache.get_default().load_icon_name ('media-eject', PLACES_ICON_SIZE); + let removeIcon = St.TextureCache.get_default().load_icon_name ('media-eject', PLACES_ICON_SIZE); let removeIconBox = new St.Button({ child: removeIcon, reactive: true }); this.actor.append(removeIconBox, Big.BoxPackFlags.NONE); diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js index 160542bc1..11008a7fe 100644 --- a/js/ui/runDialog.js +++ b/js/ui/runDialog.js @@ -7,9 +7,9 @@ const GLib = imports.gi.GLib; const Lang = imports.lang; const Mainloop = imports.mainloop; const Meta = imports.gi.Meta; +const St = imports.gi.St; const Shell = imports.gi.Shell; const Signals = imports.signals; -const St = imports.gi.St; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; diff --git a/js/ui/statusMenu.js b/js/ui/statusMenu.js index e5bf08134..e8e66d9da 100644 --- a/js/ui/statusMenu.js +++ b/js/ui/statusMenu.js @@ -36,7 +36,7 @@ StatusMenu.prototype = { this._iconBox = new St.Bin(); this.actor.add(this._iconBox, { y_align: St.Align.MIDDLE }); - let textureCache = Shell.TextureCache.get_default(); + let textureCache = St.TextureCache.get_default(); // FIXME: these icons are all wrong (likewise in createSubMenu) this._availableIcon = textureCache.load_icon_name('gtk-yes', 16); this._busyIcon = textureCache.load_icon_name('gtk-no', 16); diff --git a/src/Makefile.am b/src/Makefile.am index 99d06367d..74d58c15f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -90,8 +90,6 @@ libgnome_shell_la_SOURCES = \ shell-stack.h \ shell-tray-manager.c \ shell-tray-manager.h \ - shell-texture-cache.c \ - shell-texture-cache.h \ shell-uri-util.c \ shell-uri-util.h \ shell-window-tracker.c \ diff --git a/src/shell-app-system.c b/src/shell-app-system.c index 4d8b7c809..be07df7d3 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -14,7 +14,7 @@ #include "shell-app-private.h" #include "shell-global.h" -#include "shell-texture-cache.h" +#include "st/st-texture-cache.h" #include "display.h" #define GMENU_I_KNOW_THIS_IS_UNSTABLE @@ -1100,9 +1100,9 @@ shell_app_info_create_icon_texture (ShellAppInfo *info, float size) if (info->type == SHELL_APP_INFO_TYPE_WINDOW) { - return shell_texture_cache_bind_pixbuf_property (shell_texture_cache_get_default (), - G_OBJECT (info->window), - "icon"); + return st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (), + G_OBJECT (info->window), + "icon"); } icon = shell_app_info_get_icon (info); @@ -1113,7 +1113,7 @@ shell_app_info_create_icon_texture (ShellAppInfo *info, float size) } else { - ret = shell_texture_cache_load_gicon (shell_texture_cache_get_default (), icon, (int)size); + ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), icon, (int)size); g_object_unref (icon); } diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c index ecb99736e..ee2eb99c2 100644 --- a/src/shell-doc-system.c +++ b/src/shell-doc-system.c @@ -5,7 +5,6 @@ #include "shell-doc-system.h" #include "shell-global.h" -#include "shell-texture-cache.h" /** diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c deleted file mode 100644 index 9b022baed..000000000 --- a/src/shell-texture-cache.c +++ /dev/null @@ -1,1499 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -#include "config.h" - -#include "shell-texture-cache.h" -#include "shell-global.h" -#include -#define GNOME_DESKTOP_USE_UNSTABLE_API -#include -#include -#include - -typedef struct -{ - ShellTextureCachePolicy policy; - - /* These are exclusive */ - GIcon *icon; - gchar *uri; - gchar *thumbnail_uri; - gchar *checksum; - - /* This one is common to all */ - guint size; -} CacheKey; - -struct _ShellTextureCachePrivate -{ - /* Things that were loaded with a cache policy != NONE */ - GHashTable *keyed_cache; /* CacheKey -> CoglTexture* */ - /* Presently this is used to de-duplicate requests for GIcons, - * it could in theory be extended to async URL loading and other - * cases too. - */ - GHashTable *outstanding_requests; /* CacheKey -> AsyncTextureLoadData * */ - GnomeDesktopThumbnailFactory *thumbnails; -}; - -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; - guint base_hash; - - if (akey->icon) - base_hash = g_icon_hash (akey->icon); - else if (akey->uri) - 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; -} - -static gboolean -cache_key_equal (gconstpointer a, - gconstpointer b) -{ - CacheKey *akey = (CacheKey*)a; - CacheKey *bkey = (CacheKey*)b; - - /* We don't compare policy here, since we need - * a way to look up a cache key without respect to - * the policy. */ - - if (akey->size != bkey->size) - return FALSE; - - if (akey->icon && bkey->icon) - return g_icon_equal (akey->icon, bkey->icon); - else if (akey->uri && bkey->uri) - 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; -} - -static CacheKey * -cache_key_dup (CacheKey *key) -{ - CacheKey *ret = g_new0 (CacheKey, 1); - ret->policy = key->policy; - if (key->icon) - 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; -} - -static void -cache_key_destroy (gpointer a) -{ - CacheKey *akey = (CacheKey*)a; - if (akey->icon) - g_object_unref (akey->icon); - g_free (akey->uri); - g_free (akey->thumbnail_uri); - g_free (akey->checksum); - g_free (akey); -} - - -/* We want to preserve the aspect ratio by default, also the default - * material for an empty texture is full opacity white, which we - * definitely don't want. Skip that by setting 0 opacity. - */ -static ClutterTexture * -create_default_texture (ShellTextureCache *self) -{ - ClutterTexture * texture = CLUTTER_TEXTURE (clutter_texture_new ()); - g_object_set (texture, "keep-aspect-ratio", TRUE, "opacity", 0, NULL); - return texture; -} - -/* Reverse the opacity we added while loading */ -static void -set_texture_cogl_texture (ClutterTexture *clutter_texture, CoglHandle cogl_texture) -{ - clutter_texture_set_cogl_texture (clutter_texture, cogl_texture); - g_object_set (clutter_texture, "opacity", 255, NULL); -} - -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->keyed_cache = g_hash_table_new_full (cache_key_hash, cache_key_equal, - cache_key_destroy, cogl_handle_unref); - self->priv->outstanding_requests = g_hash_table_new_full (cache_key_hash, cache_key_equal, - cache_key_destroy, NULL); - self->priv->thumbnails = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL); -} - -static void -shell_texture_cache_dispose (GObject *object) -{ - ShellTextureCache *self = (ShellTextureCache*)object; - - if (self->priv->keyed_cache) - g_hash_table_destroy (self->priv->keyed_cache); - self->priv->keyed_cache = NULL; - - if (self->priv->thumbnails) - g_object_unref (self->priv->thumbnails); - self->priv->thumbnails = 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); -} - -typedef struct { - ShellTextureCache *cache; - char *uri; - char *mimetype; - gboolean thumbnail; - GIcon *icon; - GtkRecentInfo *recent_info; - 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; - -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); - if (data->mimetype) - g_free (data->mimetype); - if (data->recent_info) - gtk_recent_info_unref (data->recent_info); - - g_free (data); -} - -/** - * 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_data (const guchar *data, - gsize size, - int available_width, - int available_height, - GError **error) -{ - GdkPixbufLoader *pixbuf_loader = NULL; - GdkPixbuf *rotated_pixbuf = NULL; - GdkPixbuf *pixbuf; - gboolean success; - Dimensions available_dimensions; - int width_before_rotation, width_after_rotation; - - 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, data, 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, data, 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: - if (pixbuf_loader) - g_object_unref (pixbuf_loader); - return rotated_pixbuf; -} - -static GdkPixbuf * -impl_load_pixbuf_file (const char *uri, - int available_width, - int available_height, - GError **error) -{ - GdkPixbuf *pixbuf = NULL; - GFile *file; - char *contents = NULL; - gsize size; - - file = g_file_new_for_uri (uri); - if (g_file_load_contents (file, NULL, &contents, &size, NULL, error)) - { - pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size, - available_width, available_height, - error); - } - - g_object_unref (file); - g_free (contents); - - return pixbuf; -} - -static GdkPixbuf * -impl_load_thumbnail (ShellTextureCache *cache, - const char *uri, - const char *mime_type, - guint size, - GError **error) -{ - GnomeDesktopThumbnailFactory *thumbnail_factory; - GdkPixbuf *pixbuf = NULL; - GFile *file; - GFileInfo *file_info; - GTimeVal mtime_g; - time_t mtime = 0; - char *existing_thumbnail; - - file = g_file_new_for_uri (uri); - file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); - g_object_unref (file); - if (file_info) - { - g_file_info_get_modification_time (file_info, &mtime_g); - g_object_unref (file_info); - mtime = (time_t) mtime_g.tv_sec; - } - - thumbnail_factory = cache->priv->thumbnails; - - existing_thumbnail = gnome_desktop_thumbnail_factory_lookup (thumbnail_factory, uri, mtime); - - if (existing_thumbnail != NULL) - { - pixbuf = gdk_pixbuf_new_from_file_at_size (existing_thumbnail, size, size, error); - g_free (existing_thumbnail); - } - else if (gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (thumbnail_factory, uri, mtime)) - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Has failed thumbnail"); - else if (gnome_desktop_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime)) - { - pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, uri, mime_type); - if (pixbuf) - { - // we need to save the thumbnail so that we don't need to generate it again in the future - gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, pixbuf, uri, mtime); - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to generate thumbnail"); - gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, uri, mtime); - } - } - return pixbuf; -} - -static GIcon * -icon_for_mimetype (const char *mimetype) -{ - char *content_type; - GIcon *icon; - - content_type = g_content_type_from_mime_type (mimetype); - if (!content_type) - return NULL; - - icon = g_content_type_get_icon (content_type); - g_free (content_type); - return icon; -} - -static void -load_pixbuf_thread (GSimpleAsyncResult *result, - GObject *object, - GCancellable *cancellable) -{ - GdkPixbuf *pixbuf; - AsyncIconLookupData *data; - GError *error = NULL; - - data = g_object_get_data (G_OBJECT (result), "load_pixbuf_async"); - g_assert (data != NULL); - - if (data->thumbnail) - { - const char *uri; - const char *mimetype; - - if (data->recent_info) - { - uri = gtk_recent_info_get_uri (data->recent_info); - mimetype = gtk_recent_info_get_mime_type (data->recent_info); - } - else - { - uri = data->uri; - mimetype = data->mimetype; - } - pixbuf = impl_load_thumbnail (data->cache, uri, mimetype, data->width, &error); - } - else 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; - } - - if (pixbuf) - g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf), - g_object_unref); -} - -/** - * 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->cache = cache; - 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_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); - g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); - - g_object_unref (result); -} - -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->cache = cache; - 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_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); - g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); - - g_object_unref (result); -} - -static void -load_thumbnail_async (ShellTextureCache *cache, - const char *uri, - const char *mimetype, - guint size, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GSimpleAsyncResult *result; - AsyncIconLookupData *data; - - data = g_new0 (AsyncIconLookupData, 1); - data->cache = cache; - data->uri = g_strdup (uri); - data->mimetype = g_strdup (mimetype); - data->thumbnail = TRUE; - data->width = size; - data->height = size; - data->user_data = user_data; - - result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_thumbnail_async); - - g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); - g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); - - g_object_unref (result); -} - -static void -load_recent_thumbnail_async (ShellTextureCache *cache, - GtkRecentInfo *info, - guint size, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GSimpleAsyncResult *result; - AsyncIconLookupData *data; - - data = g_new0 (AsyncIconLookupData, 1); - data->cache = cache; - data->thumbnail = TRUE; - data->recent_info = gtk_recent_info_ref (info); - data->width = size; - data->height = size; - data->user_data = user_data; - - result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_recent_thumbnail_async); - - g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); - g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); - - g_object_unref (result); -} - -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 { - ShellTextureCachePolicy policy; - char *uri; - gboolean thumbnail; - char *mimetype; - GtkRecentInfo *recent_info; - char *checksum; - GIcon *icon; - GtkIconInfo *icon_info; - guint width; - guint height; - GSList *textures; -} 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), - COGL_TEXTURE_NONE, - 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 GdkPixbuf * -load_pixbuf_fallback(AsyncTextureLoadData *data) -{ - GdkPixbuf *pixbuf = NULL; - - if (data->thumbnail) - { - - GtkIconTheme *theme = gtk_icon_theme_get_default (); - - if (data->recent_info) - pixbuf = gtk_recent_info_get_icon (data->recent_info, data->width); - else - { - GIcon *icon = icon_for_mimetype (data->mimetype); - if (icon != NULL) - { - GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon (theme, - icon, - data->width, - GTK_ICON_LOOKUP_USE_BUILTIN); - g_object_unref (icon); - if (icon_info != NULL) - pixbuf = gtk_icon_info_load_icon (icon_info, NULL); - } - } - - if (pixbuf == NULL) - pixbuf = gtk_icon_theme_load_icon (theme, - "gtk-file", - data->width, - GTK_ICON_LOOKUP_USE_BUILTIN, - NULL); - } - /* Maybe we could need a fallback for outher image types? */ - - return pixbuf; -} - -static void -on_pixbuf_loaded (GObject *source, - GAsyncResult *result, - gpointer user_data) -{ - GSList *iter; - ShellTextureCache *cache; - AsyncTextureLoadData *data; - GdkPixbuf *pixbuf; - GError *error = NULL; - CoglHandle texdata = NULL; - CacheKey key; - - data = user_data; - cache = SHELL_TEXTURE_CACHE (source); - - memset (&key, 0, sizeof(key)); - key.policy = data->policy; - if (data->icon) - key.icon = data->icon; - else if (data->recent_info && data->thumbnail) - key.thumbnail_uri = (char*)gtk_recent_info_get_uri (data->recent_info); - else if (data->thumbnail) - key.thumbnail_uri = (char*)data->uri; - else if (data->uri) - key.uri = data->uri; - key.size = data->width; - - g_hash_table_remove (cache->priv->outstanding_requests, &key); - - pixbuf = load_pixbuf_async_finish (cache, result, &error); - if (pixbuf == NULL) - pixbuf = load_pixbuf_fallback(data); - if (pixbuf == NULL) - goto out; - - texdata = pixbuf_to_cogl_handle (pixbuf); - - g_object_unref (pixbuf); - - if (data->policy != SHELL_TEXTURE_CACHE_POLICY_NONE) - { - gpointer orig_key, value; - - if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, &key, - &orig_key, &value)) - { - cogl_handle_ref (texdata); - g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), - texdata); - } - } - - for (iter = data->textures; iter; iter = iter->next) - { - ClutterTexture *texture = iter->data; - set_texture_cogl_texture (texture, texdata); - } - -out: - if (texdata) - cogl_handle_unref (texdata); - if (data->icon) - { - gtk_icon_info_free (data->icon_info); - g_object_unref (data->icon); - } - else if (data->uri) - g_free (data->uri); - - if (data->recent_info) - gtk_recent_info_unref (data->recent_info); - if (data->mimetype) - g_free (data->mimetype); - - /* Alternatively we could weakref and just do nothing if the texture - is destroyed */ - for (iter = data->textures; iter; iter = iter->next) - { - ClutterTexture *texture = iter->data; - g_object_unref (texture); - } - - g_clear_error (&error); - g_free (data); -} - -typedef struct { - ShellTextureCache *cache; - ClutterTexture *texture; - GObject *source; - guint notify_signal_id; - gboolean weakref_active; -} ShellTextureCachePropertyBind; - -static void -shell_texture_cache_reset_texture (ShellTextureCachePropertyBind *bind, const char *propname) -{ - GdkPixbuf *pixbuf; - CoglHandle texdata; - - g_object_get (bind->source, propname, &pixbuf, NULL); - - g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); - - if (pixbuf != NULL) - { - texdata = pixbuf_to_cogl_handle (pixbuf); - g_object_unref (pixbuf); - - clutter_texture_set_cogl_texture (bind->texture, texdata); - cogl_handle_unref (texdata); - - clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 255); - } - else - clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 0); -} - -static void -shell_texture_cache_on_pixbuf_notify (GObject *object, - GParamSpec *paramspec, - gpointer data) -{ - ShellTextureCachePropertyBind *bind = data; - shell_texture_cache_reset_texture (bind, paramspec->name); -} - -static void -shell_texture_cache_bind_weak_notify (gpointer data, - GObject *source_location) -{ - ShellTextureCachePropertyBind *bind = data; - bind->weakref_active = FALSE; - g_signal_handler_disconnect (bind->source, bind->notify_signal_id); -} - -static void -shell_texture_cache_free_bind (gpointer data) -{ - ShellTextureCachePropertyBind *bind = data; - if (bind->weakref_active) - g_object_weak_unref (G_OBJECT(bind->texture), shell_texture_cache_bind_weak_notify, bind); - g_free (bind); -} - -/** - * shell_texture_cache_bind_pixbuf_property: - * @cache: - * @object: A #GObject with a property @property_name of type #GdkPixbuf - * @property_name: Name of a property - * - * Create a #ClutterTexture which tracks the #GdkPixbuf value of a GObject property - * named by @property_name. Unlike other methods in ShellTextureCache, the underlying - * CoglHandle is not shared by default with other invocations to this method. - * - * If the source object is destroyed, the texture will continue to show the last - * value of the property. - * - * Return value: (transfer none): A new #ClutterActor - */ -ClutterActor * -shell_texture_cache_bind_pixbuf_property (ShellTextureCache *cache, - GObject *object, - const char *property_name) -{ - ClutterTexture *texture; - gchar *notify_key; - ShellTextureCachePropertyBind *bind; - - texture = CLUTTER_TEXTURE (clutter_texture_new ()); - - bind = g_new0 (ShellTextureCachePropertyBind, 1); - bind->cache = cache; - bind->texture = texture; - bind->source = object; - g_object_weak_ref (G_OBJECT (texture), shell_texture_cache_bind_weak_notify, bind); - bind->weakref_active = TRUE; - - shell_texture_cache_reset_texture (bind, property_name); - - notify_key = g_strdup_printf ("notify::%s", property_name); - bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(shell_texture_cache_on_pixbuf_notify), - bind, (GClosureNotify)shell_texture_cache_free_bind, 0); - g_free (notify_key); - - return CLUTTER_ACTOR(texture); -} - -/** - * create_texture_and_ensure_request: - * @cache: - * @key: A filled in #CacheKey - * @request: (out): If no request is outstanding, one will be created and returned here - * @texture: (out): A new texture, also added to the request - * - * Check for any outstanding load for the data represented by @key. If there - * is already a request pending, append it to that request to avoid loading - * the data multiple times. - * - * Returns: %TRUE iff there is already a request pending - */ -static gboolean -create_texture_and_ensure_request (ShellTextureCache *cache, - CacheKey *key, - AsyncTextureLoadData **request, - ClutterActor **texture) -{ - CoglHandle texdata; - AsyncTextureLoadData *pending; - gboolean had_pending; - - *texture = (ClutterActor *) create_default_texture (cache); - clutter_actor_set_size (*texture, key->size, key->size); - - texdata = g_hash_table_lookup (cache->priv->keyed_cache, key); - - if (texdata != NULL) - { - /* We had this cached already, just set the texture and we're done. */ - set_texture_cogl_texture (CLUTTER_TEXTURE (*texture), texdata); - return TRUE; - } - - pending = g_hash_table_lookup (cache->priv->outstanding_requests, key); - had_pending = pending != NULL; - - if (pending == NULL) - { - /* Not cached and no pending request, create it */ - *request = g_new0 (AsyncTextureLoadData, 1); - g_hash_table_insert (cache->priv->outstanding_requests, cache_key_dup (key), *request); - } - else - *request = pending; - - /* Regardless of whether there was a pending request, prepend our texture here. */ - (*request)->textures = g_slist_prepend ((*request)->textures, g_object_ref (*texture)); - - return had_pending; -} - -/** - * 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) -{ - AsyncTextureLoadData *request; - ClutterActor *texture; - CacheKey key; - GtkIconTheme *theme; - GtkIconInfo *info; - - memset (&key, 0, sizeof(key)); - key.icon = icon; - key.size = size; - - if (create_texture_and_ensure_request (cache, &key, &request, &texture)) - return texture; - - /* 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) - { - /* hardcoded here for now; we should actually blow this away on - * icon theme changes probably */ - request->policy = SHELL_TEXTURE_CACHE_POLICY_FOREVER; - request->icon = g_object_ref (icon); - request->icon_info = info; - request->width = request->height = size; - - load_icon_pixbuf_async (cache, icon, info, size, NULL, on_pixbuf_loaded, request); - } - else - { - /* Blah; we failed to find the icon, but we've added our texture to the outstanding - * requests. In that case, just undo what create_texture_lookup_status did. - */ - g_slist_foreach (request->textures, (GFunc) g_object_unref, NULL); - g_slist_free (request->textures); - g_free (request); - g_hash_table_remove (cache->priv->outstanding_requests, &key); - } - - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_load_icon_name: - * @cache: The texture cache instance - * @name: Name of a themed icon - * @size: Size of themed - * - * Load a themed icon into a texture. - * - * Return Value: (transfer none): A new #ClutterTexture for the icon - */ -ClutterActor * -shell_texture_cache_load_icon_name (ShellTextureCache *cache, - const char *name, - gint size) -{ - ClutterActor *texture; - GIcon *themed; - - themed = g_themed_icon_new (name); - texture = shell_texture_cache_load_gicon (cache, themed, size); - g_object_unref (themed); - - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_load_uri_async: - * - * @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 = create_default_texture (cache); - - data = g_new0 (AsyncTextureLoadData, 1); - data->policy = SHELL_TEXTURE_CACHE_POLICY_NONE; - data->uri = g_strdup (uri); - data->width = available_width; - data->height = available_height; - data->textures = g_slist_prepend (data->textures, 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 - * @policy: Requested lifecycle of cached data - * @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, - ShellTextureCachePolicy policy, - const gchar *uri, - int available_width, - int available_height, - GError **error) -{ - ClutterTexture *texture; - CoglHandle texdata; - GdkPixbuf *pixbuf; - CacheKey key; - - texture = create_default_texture (cache); - - memset (&key, 0, sizeof (CacheKey)); - key.policy = policy; - key.uri = (char*)uri; - key.size = available_width; - texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); - - if (texdata == NULL) - { - pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, 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); - - if (policy == SHELL_TEXTURE_CACHE_POLICY_FOREVER) - { - g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata); - } - else - cogl_handle_unref (texdata); - } - else - set_texture_cogl_texture (texture, texdata); - - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_load_from_data: - * @cache: The texture cache instance - * @data: Image data in PNG, GIF, etc format - * @len: length of @data - * @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 - * 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. - * - * Return value: (transfer none): A new #ClutterActor with the image data loaded if it was - * generated succesfully, %NULL otherwise - */ -ClutterActor * -shell_texture_cache_load_from_data (ShellTextureCache *cache, - const guchar *data, - gsize len, - int size, - GError **error) -{ - ClutterTexture *texture; - CoglHandle texdata; - GdkPixbuf *pixbuf; - CacheKey key; - gchar *checksum; - - texture = create_default_texture (cache); - clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); - - 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) - { - 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); - } - - g_free (key.checksum); - - set_texture_cogl_texture (texture, texdata); - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_load_from_raw: - * @cache: a #ShellTextureCache - * @data: raw pixel data - * @len: the length of @data - * @has_alpha: whether @data includes an alpha channel - * @width: width in pixels of @data - * @height: width in pixels of @data - * @rowstride: rowstride of @data - * @size: size of icon to return - * - * Creates (or retrieves from cache) an icon based on raw pixel data. - * - * Return value: (transfer none): a new #ClutterActor displaying a - * pixbuf created from @data and the other parameters. - **/ -ClutterActor * -shell_texture_cache_load_from_raw (ShellTextureCache *cache, - const guchar *data, - gsize len, - gboolean has_alpha, - int width, - int height, - int rowstride, - int size, - GError **error) -{ - ClutterTexture *texture; - CoglHandle texdata; - CacheKey key; - gchar *checksum; - - texture = create_default_texture (cache); - clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); - - /* In theory, two images of different size could have the same - * pixel data. We ignore that theory. - */ - 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) - { - texdata = cogl_texture_new_from_data (width, height, COGL_TEXTURE_NONE, - has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, - COGL_PIXEL_FORMAT_ANY, - rowstride, data); - g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata); - } - - g_free (key.checksum); - - set_texture_cogl_texture (texture, texdata); - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_load_thumbnail: - * @cache: - * @size: Size in pixels to use for thumbnail - * @uri: Source URI - * @mimetype: Source mime type - * - * Asynchronously load a thumbnail image of a URI into a texture. The - * returned texture object will be a new instance; however, its texture data - * may be shared with other objects. This implies the texture data is cached. - * - * The current caching policy is permanent; to uncache, you must explicitly - * call shell_texture_cache_unref_thumbnail(). - * - * Returns: (transfer none): A new #ClutterActor - */ -ClutterActor * -shell_texture_cache_load_thumbnail (ShellTextureCache *cache, - int size, - const char *uri, - const char *mimetype) -{ - ClutterTexture *texture; - AsyncTextureLoadData *data; - CacheKey key; - CoglHandle texdata; - - /* Don't attempt to load thumbnails for non-local URIs */ - if (!g_str_has_prefix (uri, "file://")) - { - GIcon *icon = icon_for_mimetype (mimetype); - return shell_texture_cache_load_gicon (cache, icon, size); - } - - texture = create_default_texture (cache); - clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); - - memset (&key, 0, sizeof(key)); - key.size = size; - key.thumbnail_uri = (char*)uri; - - texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); - if (!texdata) - { - data = g_new0 (AsyncTextureLoadData, 1); - data->policy = SHELL_TEXTURE_CACHE_POLICY_FOREVER; - data->uri = g_strdup (uri); - data->mimetype = g_strdup (mimetype); - data->thumbnail = TRUE; - data->width = size; - data->height = size; - data->textures = g_slist_prepend (data->textures, g_object_ref (texture)); - load_thumbnail_async (cache, uri, mimetype, size, NULL, on_pixbuf_loaded, data); - } - else - { - set_texture_cogl_texture (texture, texdata); - } - - return CLUTTER_ACTOR (texture); -} - -static GIcon * -icon_for_recent (GtkRecentInfo *info) -{ - const char *mimetype; - - mimetype = gtk_recent_info_get_mime_type (info); - if (!mimetype) - { - return g_themed_icon_new (GTK_STOCK_FILE); - } - - return icon_for_mimetype (mimetype); -} - -/** - * shell_texture_cache_load_recent_thumbnail: - * @cache: - * @size: Size in pixels to use for thumbnail - * @info: Recent item info - * - * Asynchronously load a thumbnail image of a #GtkRecentInfo into a texture. The - * returned texture object will be a new instance; however, its texture data - * may be shared with other objects. This implies the texture data is cached. - * - * The current caching policy is permanent; to uncache, you must explicitly - * call shell_texture_cache_unref_recent_thumbnail(). - * - * Returns: (transfer none): A new #ClutterActor - */ -ClutterActor * -shell_texture_cache_load_recent_thumbnail (ShellTextureCache *cache, - int size, - GtkRecentInfo *info) -{ - ClutterTexture *texture; - AsyncTextureLoadData *data; - CacheKey key; - CoglHandle texdata; - const char *uri; - - uri = gtk_recent_info_get_uri (info); - - /* Don't attempt to load thumbnails for non-local URIs */ - if (!g_str_has_prefix (uri, "file://")) - { - GIcon *icon = icon_for_recent (info); - return shell_texture_cache_load_gicon (cache, icon, size); - } - - texture = CLUTTER_TEXTURE (clutter_texture_new ()); - clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); - - memset (&key, 0, sizeof(key)); - key.size = size; - key.thumbnail_uri = (char*)gtk_recent_info_get_uri (info); - - texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); - if (!texdata) - { - data = g_new0 (AsyncTextureLoadData, 1); - data->policy = SHELL_TEXTURE_CACHE_POLICY_FOREVER; - data->thumbnail = TRUE; - data->recent_info = gtk_recent_info_ref (info); - data->width = size; - data->height = size; - data->textures = g_slist_prepend (data->textures, g_object_ref (texture)); - load_recent_thumbnail_async (cache, info, size, NULL, on_pixbuf_loaded, data); - } - else - { - set_texture_cogl_texture (texture, texdata); - } - - return CLUTTER_ACTOR (texture); -} - -/** - * shell_texture_cache_evict_thumbnail: - * @cache: - * @uri: Source URI - * - * Removes all references added by shell_texture_cache_load_thumbnail() function - * created for the given URI. - */ -void -shell_texture_cache_evict_thumbnail (ShellTextureCache *cache, - const char *uri) -{ - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, cache->priv->keyed_cache); - - while (g_hash_table_iter_next (&iter, &key, &value)) - { - CacheKey *cachekey = key; - - if (cachekey->thumbnail_uri == NULL || strcmp (cachekey->thumbnail_uri, uri) != 0) - continue; - - g_hash_table_iter_remove (&iter); - } -} - -/** - * shell_texture_cache_evict_recent_thumbnail: - * @cache: - * @info: A recent info - * - * Removes all references added by shell_texture_cache_load_recent_thumbnail() function - * for the URI associated with the given @info. - */ -void -shell_texture_cache_evict_recent_thumbnail (ShellTextureCache *cache, - GtkRecentInfo *info) -{ - shell_texture_cache_evict_thumbnail (cache, gtk_recent_info_get_uri (info)); -} - -static size_t -pixbuf_byte_size (GdkPixbuf *pixbuf) -{ - /* This bit translated from gtk+/gdk-pixbuf/gdk-pixbuf.c:gdk_pixbuf_copy. The comment - * there was: - * - * Calculate a semi-exact size. Here we copy with full rowstrides; - * maybe we should copy each row individually with the minimum - * rowstride? - */ - return (gdk_pixbuf_get_height (pixbuf) - 1) * gdk_pixbuf_get_rowstride (pixbuf) + - + gdk_pixbuf_get_width (pixbuf) * ((gdk_pixbuf_get_n_channels (pixbuf)* gdk_pixbuf_get_bits_per_sample (pixbuf) + 7) / 8); -} - -/** - * shell_texture_cache_pixbuf_equal: - * - * Returns: %TRUE iff the given pixbufs are bytewise-equal - */ -gboolean -shell_texture_cache_pixbuf_equal (ShellTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b) -{ - size_t size_a = pixbuf_byte_size (a); - size_t size_b = pixbuf_byte_size (b); - if (size_a != size_b) - return FALSE; - return memcmp (gdk_pixbuf_get_pixels (a), gdk_pixbuf_get_pixels (b), size_a) == 0; -} - -static ShellTextureCache *instance = NULL; - -/** - * shell_texture_cache_get_default: - * - * Return value: (transfer none): The global texture cache - */ -ShellTextureCache* -shell_texture_cache_get_default (void) -{ - 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 deleted file mode 100644 index d7280c465..000000000 --- a/src/shell-texture-cache.h +++ /dev/null @@ -1,100 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -#ifndef __SHELL_TEXTURE_CACHE_H__ -#define __SHELL_TEXTURE_CACHE_H__ - -#include -#include -#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; - -}; - -typedef enum { - SHELL_TEXTURE_CACHE_POLICY_NONE, - SHELL_TEXTURE_CACHE_POLICY_FOREVER -} ShellTextureCachePolicy; - -GType shell_texture_cache_get_type (void) G_GNUC_CONST; - -ShellTextureCache* shell_texture_cache_get_default (void); - -ClutterActor *shell_texture_cache_bind_pixbuf_property (ShellTextureCache *cache, - GObject *object, - const char *property_name); - -ClutterActor *shell_texture_cache_load_icon_name (ShellTextureCache *cache, - const char *name, - gint size); - -ClutterActor *shell_texture_cache_load_gicon (ShellTextureCache *cache, - GIcon *icon, - gint size); - -ClutterActor *shell_texture_cache_load_thumbnail (ShellTextureCache *cache, - int size, - const char *uri, - const char *mimetype); - -ClutterActor *shell_texture_cache_load_recent_thumbnail (ShellTextureCache *cache, - int size, - GtkRecentInfo *info); - -void shell_texture_cache_evict_thumbnail (ShellTextureCache *cache, - const char *uri); - -void shell_texture_cache_evict_recent_thumbnail (ShellTextureCache *cache, - GtkRecentInfo *info); - -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, - ShellTextureCachePolicy policy, - const gchar *filename, - int available_width, - int available_height, - GError **error); - -ClutterActor *shell_texture_cache_load_from_data (ShellTextureCache *cache, - const guchar *data, - gsize len, - int size, - GError **error); -ClutterActor *shell_texture_cache_load_from_raw (ShellTextureCache *cache, - const guchar *data, - gsize len, - gboolean has_alpha, - int width, - int height, - int rowstride, - int size, - GError **error); - -gboolean shell_texture_cache_pixbuf_equal (ShellTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b); - -#endif /* __SHELL_TEXTURE_CACHE_H__ */ diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c index 547c34147..2dd36b9c3 100644 --- a/src/shell-window-tracker.c +++ b/src/shell-window-tracker.c @@ -16,7 +16,7 @@ #include "shell-window-tracker.h" #include "shell-app-system.h" #include "shell-app-private.h" -#include "shell-texture-cache.h" +#include "st/st-texture-cache.h" #include "shell-global.h" #include "shell-marshal.h" @@ -847,8 +847,8 @@ shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size) } themed = g_themed_icon_new (icon_name); - texture = shell_texture_cache_load_gicon (shell_texture_cache_get_default (), - themed, size); + texture = st_texture_cache_load_gicon (st_texture_cache_get_default (), + themed, size); g_object_unref (G_OBJECT (themed)); return texture; } diff --git a/src/st/st-entry.c b/src/st/st-entry.c index 6cb3cbfc8..e4749028e 100644 --- a/src/st/st-entry.c +++ b/src/st/st-entry.c @@ -842,9 +842,7 @@ _st_entry_set_icon_from_file (StEntry *entry, cache = st_texture_cache_get_default (); - - - *icon = (ClutterActor*) st_texture_cache_get_texture (cache, filename); + *icon = (ClutterActor*) st_texture_cache_load_file_simple (cache, filename); clutter_actor_set_reactive (*icon, TRUE); clutter_actor_set_parent (*icon, CLUTTER_ACTOR (entry)); diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c index 6fca08dd7..ddf894658 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -1,451 +1,1532 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * st-widget.h: Base class for St actors - * - * Copyright 2007 OpenedHand - * Copyright 2009 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 2.1, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - * Boston, MA 02111-1307, USA. - * - */ -/** - * SECTION:st-texture-cache - * @short_description: A per-process store to cache textures - * - * #StTextureCache allows an application to re-use an previously loaded - * textures. - */ - -#ifdef HAVE_CONFIG_H #include "config.h" -#endif - -#include -#include -#include -#include #include "st-texture-cache.h" -#include "st-marshal.h" -#include "st-private.h" -#include "st-subtexture.h" -G_DEFINE_TYPE (StTextureCache, st_texture_cache, G_TYPE_OBJECT) +#include +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include +#include +#include -#define TEXTURE_CACHE_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_TEXTURE_CACHE, StTextureCachePrivate)) +typedef struct +{ + StTextureCachePolicy policy; -typedef struct _StTextureCachePrivate StTextureCachePrivate; + /* These are exclusive */ + GIcon *icon; + gchar *uri; + gchar *thumbnail_uri; + gchar *checksum; + + /* This one is common to all */ + guint size; +} CacheKey; struct _StTextureCachePrivate { - GHashTable *cache; + /* Things that were loaded with a cache policy != NONE */ + GHashTable *keyed_cache; /* CacheKey -> CoglTexture* */ + /* Presently this is used to de-duplicate requests for GIcons, + * it could in theory be extended to async URL loading and other + * cases too. + */ + GHashTable *outstanding_requests; /* CacheKey -> AsyncTextureLoadData * */ + GnomeDesktopThumbnailFactory *thumbnails; }; -typedef struct FinalizedClosure +static void st_texture_cache_dispose (GObject *object); +static void st_texture_cache_finalize (GObject *object); + +G_DEFINE_TYPE(StTextureCache, st_texture_cache, G_TYPE_OBJECT); + +static guint +cache_key_hash (gconstpointer a) { - gchar *path; - StTextureCache *cache; -} FinalizedClosure; + CacheKey *akey = (CacheKey *)a; + guint base_hash; -enum + if (akey->icon) + base_hash = g_icon_hash (akey->icon); + else if (akey->uri) + 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; +} + +static gboolean +cache_key_equal (gconstpointer a, + gconstpointer b) { - PROP_0, -}; + CacheKey *akey = (CacheKey*)a; + CacheKey *bkey = (CacheKey*)b; -static StTextureCache* __cache_singleton = NULL; + /* We don't compare policy here, since we need + * a way to look up a cache key without respect to + * the policy. */ -/* - * Convention: posX with a value of -1 indicates whole texture + if (akey->size != bkey->size) + return FALSE; + + if (akey->icon && bkey->icon) + return g_icon_equal (akey->icon, bkey->icon); + else if (akey->uri && bkey->uri) + 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; +} + +static CacheKey * +cache_key_dup (CacheKey *key) +{ + CacheKey *ret = g_new0 (CacheKey, 1); + ret->policy = key->policy; + if (key->icon) + 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; +} + +static void +cache_key_destroy (gpointer a) +{ + CacheKey *akey = (CacheKey*)a; + if (akey->icon) + g_object_unref (akey->icon); + g_free (akey->uri); + g_free (akey->thumbnail_uri); + g_free (akey->checksum); + g_free (akey); +} + + +/* We want to preserve the aspect ratio by default, also the default + * material for an empty texture is full opacity white, which we + * definitely don't want. Skip that by setting 0 opacity. */ -typedef struct StTextureCacheItem { - char filename[256]; - int width, height; - int posX, posY; - ClutterActor *ptr; -} StTextureCacheItem; - -static StTextureCacheItem * -st_texture_cache_item_new (void) +static ClutterTexture * +create_default_texture (StTextureCache *self) { - return g_slice_new0 (StTextureCacheItem); + ClutterTexture * texture = CLUTTER_TEXTURE (clutter_texture_new ()); + g_object_set (texture, "keep-aspect-ratio", TRUE, "opacity", 0, NULL); + return texture; } +/* Reverse the opacity we added while loading */ static void -st_texture_cache_item_free (StTextureCacheItem *item) +set_texture_cogl_texture (ClutterTexture *clutter_texture, CoglHandle cogl_texture) { - g_slice_free (StTextureCacheItem, item); -} - -static void -st_texture_cache_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -st_texture_cache_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -st_texture_cache_dispose (GObject *object) -{ - if (G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose) - G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object); -} - -static void -st_texture_cache_finalize (GObject *object) -{ - StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(object); - - if (priv->cache) - { - g_hash_table_unref (priv->cache); - priv->cache = NULL; - } - - G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object); + clutter_texture_set_cogl_texture (clutter_texture, cogl_texture); + g_object_set (clutter_texture, "opacity", 255, NULL); } static void st_texture_cache_class_init (StTextureCacheClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - g_type_class_add_private (klass, sizeof (StTextureCachePrivate)); - - object_class->get_property = st_texture_cache_get_property; - object_class->set_property = st_texture_cache_set_property; - object_class->dispose = st_texture_cache_dispose; - object_class->finalize = st_texture_cache_finalize; + GObjectClass *gobject_class = (GObjectClass *)klass; + gobject_class->dispose = st_texture_cache_dispose; + gobject_class->finalize = st_texture_cache_finalize; } static void st_texture_cache_init (StTextureCache *self) { - StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); - - priv->cache = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - + self->priv = g_new0 (StTextureCachePrivate, 1); + self->priv->keyed_cache = g_hash_table_new_full (cache_key_hash, cache_key_equal, + cache_key_destroy, cogl_handle_unref); + self->priv->outstanding_requests = g_hash_table_new_full (cache_key_hash, cache_key_equal, + cache_key_destroy, NULL); + self->priv->thumbnails = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL); } +static void +st_texture_cache_dispose (GObject *object) +{ + StTextureCache *self = (StTextureCache*)object; + + if (self->priv->keyed_cache) + g_hash_table_destroy (self->priv->keyed_cache); + self->priv->keyed_cache = NULL; + + if (self->priv->thumbnails) + g_object_unref (self->priv->thumbnails); + self->priv->thumbnails = NULL; + + G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object); +} + +static void +st_texture_cache_finalize (GObject *object) +{ + G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object); +} + +typedef struct { + StTextureCache *cache; + char *uri; + char *mimetype; + gboolean thumbnail; + GIcon *icon; + GtkRecentInfo *recent_info; + 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; + +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); + if (data->mimetype) + g_free (data->mimetype); + if (data->recent_info) + gtk_recent_info_unref (data->recent_info); + + g_free (data); +} + +/** + * 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_data (const guchar *data, + gsize size, + int available_width, + int available_height, + GError **error) +{ + GdkPixbufLoader *pixbuf_loader = NULL; + GdkPixbuf *rotated_pixbuf = NULL; + GdkPixbuf *pixbuf; + gboolean success; + Dimensions available_dimensions; + int width_before_rotation, width_after_rotation; + + 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, data, 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, data, 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: + if (pixbuf_loader) + g_object_unref (pixbuf_loader); + return rotated_pixbuf; +} + +static GdkPixbuf * +impl_load_pixbuf_file (const char *uri, + int available_width, + int available_height, + GError **error) +{ + GdkPixbuf *pixbuf = NULL; + GFile *file; + char *contents = NULL; + gsize size; + + file = g_file_new_for_uri (uri); + if (g_file_load_contents (file, NULL, &contents, &size, NULL, error)) + { + pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size, + available_width, available_height, + error); + } + + g_object_unref (file); + g_free (contents); + + return pixbuf; +} + +static GdkPixbuf * +impl_load_thumbnail (StTextureCache *cache, + const char *uri, + const char *mime_type, + guint size, + GError **error) +{ + GnomeDesktopThumbnailFactory *thumbnail_factory; + GdkPixbuf *pixbuf = NULL; + GFile *file; + GFileInfo *file_info; + GTimeVal mtime_g; + time_t mtime = 0; + char *existing_thumbnail; + + file = g_file_new_for_uri (uri); + file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); + g_object_unref (file); + if (file_info) + { + g_file_info_get_modification_time (file_info, &mtime_g); + g_object_unref (file_info); + mtime = (time_t) mtime_g.tv_sec; + } + + thumbnail_factory = cache->priv->thumbnails; + + existing_thumbnail = gnome_desktop_thumbnail_factory_lookup (thumbnail_factory, uri, mtime); + + if (existing_thumbnail != NULL) + { + pixbuf = gdk_pixbuf_new_from_file_at_size (existing_thumbnail, size, size, error); + g_free (existing_thumbnail); + } + else if (gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (thumbnail_factory, uri, mtime)) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Has failed thumbnail"); + else if (gnome_desktop_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime)) + { + pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, uri, mime_type); + if (pixbuf) + { + // we need to save the thumbnail so that we don't need to generate it again in the future + gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, pixbuf, uri, mtime); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to generate thumbnail"); + gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, uri, mtime); + } + } + return pixbuf; +} + +static GIcon * +icon_for_mimetype (const char *mimetype) +{ + char *content_type; + GIcon *icon; + + content_type = g_content_type_from_mime_type (mimetype); + if (!content_type) + return NULL; + + icon = g_content_type_get_icon (content_type); + g_free (content_type); + return icon; +} + +static void +load_pixbuf_thread (GSimpleAsyncResult *result, + GObject *object, + GCancellable *cancellable) +{ + GdkPixbuf *pixbuf; + AsyncIconLookupData *data; + GError *error = NULL; + + data = g_object_get_data (G_OBJECT (result), "load_pixbuf_async"); + g_assert (data != NULL); + + if (data->thumbnail) + { + const char *uri; + const char *mimetype; + + if (data->recent_info) + { + uri = gtk_recent_info_get_uri (data->recent_info); + mimetype = gtk_recent_info_get_mime_type (data->recent_info); + } + else + { + uri = data->uri; + mimetype = data->mimetype; + } + pixbuf = impl_load_thumbnail (data->cache, uri, mimetype, data->width, &error); + } + else 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; + } + + if (pixbuf) + g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf), + g_object_unref); +} + +/** + * 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 (StTextureCache *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->cache = cache; + 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_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); + g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); +} + +static void +load_uri_pixbuf_async (StTextureCache *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->cache = cache; + 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_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); + g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); +} + +static void +load_thumbnail_async (StTextureCache *cache, + const char *uri, + const char *mimetype, + guint size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + AsyncIconLookupData *data; + + data = g_new0 (AsyncIconLookupData, 1); + data->cache = cache; + data->uri = g_strdup (uri); + data->mimetype = g_strdup (mimetype); + data->thumbnail = TRUE; + data->width = size; + data->height = size; + data->user_data = user_data; + + result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_thumbnail_async); + + g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); + g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); +} + +static void +load_recent_thumbnail_async (StTextureCache *cache, + GtkRecentInfo *info, + guint size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + AsyncIconLookupData *data; + + data = g_new0 (AsyncIconLookupData, 1); + data->cache = cache; + data->thumbnail = TRUE; + data->recent_info = gtk_recent_info_ref (info); + data->width = size; + data->height = size; + data->user_data = user_data; + + result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_recent_thumbnail_async); + + g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy); + g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); +} + +static GdkPixbuf * +load_pixbuf_async_finish (StTextureCache *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 { + StTextureCachePolicy policy; + char *uri; + gboolean thumbnail; + char *mimetype; + GtkRecentInfo *recent_info; + char *checksum; + GIcon *icon; + GtkIconInfo *icon_info; + guint width; + guint height; + GSList *textures; +} 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), + COGL_TEXTURE_NONE, + 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 GdkPixbuf * +load_pixbuf_fallback(AsyncTextureLoadData *data) +{ + GdkPixbuf *pixbuf = NULL; + + if (data->thumbnail) + { + + GtkIconTheme *theme = gtk_icon_theme_get_default (); + + if (data->recent_info) + pixbuf = gtk_recent_info_get_icon (data->recent_info, data->width); + else + { + GIcon *icon = icon_for_mimetype (data->mimetype); + if (icon != NULL) + { + GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon (theme, + icon, + data->width, + GTK_ICON_LOOKUP_USE_BUILTIN); + g_object_unref (icon); + if (icon_info != NULL) + pixbuf = gtk_icon_info_load_icon (icon_info, NULL); + } + } + + if (pixbuf == NULL) + pixbuf = gtk_icon_theme_load_icon (theme, + "gtk-file", + data->width, + GTK_ICON_LOOKUP_USE_BUILTIN, + NULL); + } + /* Maybe we could need a fallback for outher image types? */ + + return pixbuf; +} + +static void +on_pixbuf_loaded (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSList *iter; + StTextureCache *cache; + AsyncTextureLoadData *data; + GdkPixbuf *pixbuf; + GError *error = NULL; + CoglHandle texdata = NULL; + CacheKey key; + + data = user_data; + cache = ST_TEXTURE_CACHE (source); + + memset (&key, 0, sizeof(key)); + key.policy = data->policy; + if (data->icon) + key.icon = data->icon; + else if (data->recent_info && data->thumbnail) + key.thumbnail_uri = (char*)gtk_recent_info_get_uri (data->recent_info); + else if (data->thumbnail) + key.thumbnail_uri = (char*)data->uri; + else if (data->uri) + key.uri = data->uri; + key.size = data->width; + + g_hash_table_remove (cache->priv->outstanding_requests, &key); + + pixbuf = load_pixbuf_async_finish (cache, result, &error); + if (pixbuf == NULL) + pixbuf = load_pixbuf_fallback(data); + if (pixbuf == NULL) + goto out; + + texdata = pixbuf_to_cogl_handle (pixbuf); + + g_object_unref (pixbuf); + + if (data->policy != ST_TEXTURE_CACHE_POLICY_NONE) + { + gpointer orig_key, value; + + if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, &key, + &orig_key, &value)) + { + cogl_handle_ref (texdata); + g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), + texdata); + } + } + + for (iter = data->textures; iter; iter = iter->next) + { + ClutterTexture *texture = iter->data; + set_texture_cogl_texture (texture, texdata); + } + +out: + if (texdata) + cogl_handle_unref (texdata); + if (data->icon) + { + gtk_icon_info_free (data->icon_info); + g_object_unref (data->icon); + } + else if (data->uri) + g_free (data->uri); + + if (data->recent_info) + gtk_recent_info_unref (data->recent_info); + if (data->mimetype) + g_free (data->mimetype); + + /* Alternatively we could weakref and just do nothing if the texture + is destroyed */ + for (iter = data->textures; iter; iter = iter->next) + { + ClutterTexture *texture = iter->data; + g_object_unref (texture); + } + + g_clear_error (&error); + g_free (data); +} + +typedef struct { + StTextureCache *cache; + ClutterTexture *texture; + GObject *source; + guint notify_signal_id; + gboolean weakref_active; +} StTextureCachePropertyBind; + +static void +st_texture_cache_reset_texture (StTextureCachePropertyBind *bind, + const char *propname) +{ + GdkPixbuf *pixbuf; + CoglHandle texdata; + + g_object_get (bind->source, propname, &pixbuf, NULL); + + g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); + + if (pixbuf != NULL) + { + texdata = pixbuf_to_cogl_handle (pixbuf); + g_object_unref (pixbuf); + + clutter_texture_set_cogl_texture (bind->texture, texdata); + cogl_handle_unref (texdata); + + clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 255); + } + else + clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 0); +} + +static void +st_texture_cache_on_pixbuf_notify (GObject *object, + GParamSpec *paramspec, + gpointer data) +{ + StTextureCachePropertyBind *bind = data; + st_texture_cache_reset_texture (bind, paramspec->name); +} + +static void +st_texture_cache_bind_weak_notify (gpointer data, + GObject *source_location) +{ + StTextureCachePropertyBind *bind = data; + bind->weakref_active = FALSE; + g_signal_handler_disconnect (bind->source, bind->notify_signal_id); +} + +static void +st_texture_cache_free_bind (gpointer data) +{ + StTextureCachePropertyBind *bind = data; + if (bind->weakref_active) + g_object_weak_unref (G_OBJECT(bind->texture), st_texture_cache_bind_weak_notify, bind); + g_free (bind); +} + +/** + * st_texture_cache_bind_pixbuf_property: + * @cache: + * @object: A #GObject with a property @property_name of type #GdkPixbuf + * @property_name: Name of a property + * + * Create a #ClutterTexture which tracks the #GdkPixbuf value of a GObject property + * named by @property_name. Unlike other methods in StTextureCache, the underlying + * CoglHandle is not shared by default with other invocations to this method. + * + * If the source object is destroyed, the texture will continue to show the last + * value of the property. + * + * Return value: (transfer none): A new #ClutterActor + */ +ClutterActor * +st_texture_cache_bind_pixbuf_property (StTextureCache *cache, + GObject *object, + const char *property_name) +{ + ClutterTexture *texture; + gchar *notify_key; + StTextureCachePropertyBind *bind; + + texture = CLUTTER_TEXTURE (clutter_texture_new ()); + + bind = g_new0 (StTextureCachePropertyBind, 1); + bind->cache = cache; + bind->texture = texture; + bind->source = object; + g_object_weak_ref (G_OBJECT (texture), st_texture_cache_bind_weak_notify, bind); + bind->weakref_active = TRUE; + + st_texture_cache_reset_texture (bind, property_name); + + notify_key = g_strdup_printf ("notify::%s", property_name); + bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(st_texture_cache_on_pixbuf_notify), + bind, (GClosureNotify)st_texture_cache_free_bind, 0); + g_free (notify_key); + + return CLUTTER_ACTOR(texture); +} + +/** + * create_texture_and_ensure_request: + * @cache: + * @key: A filled in #CacheKey + * @request: (out): If no request is outstanding, one will be created and returned here + * @texture: (out): A new texture, also added to the request + * + * Check for any outstanding load for the data represented by @key. If there + * is already a request pending, append it to that request to avoid loading + * the data multiple times. + * + * Returns: %TRUE iff there is already a request pending + */ +static gboolean +create_texture_and_ensure_request (StTextureCache *cache, + CacheKey *key, + AsyncTextureLoadData **request, + ClutterActor **texture) +{ + CoglHandle texdata; + AsyncTextureLoadData *pending; + gboolean had_pending; + + *texture = (ClutterActor *) create_default_texture (cache); + clutter_actor_set_size (*texture, key->size, key->size); + + texdata = g_hash_table_lookup (cache->priv->keyed_cache, key); + + if (texdata != NULL) + { + /* We had this cached already, just set the texture and we're done. */ + set_texture_cogl_texture (CLUTTER_TEXTURE (*texture), texdata); + return TRUE; + } + + pending = g_hash_table_lookup (cache->priv->outstanding_requests, key); + had_pending = pending != NULL; + + if (pending == NULL) + { + /* Not cached and no pending request, create it */ + *request = g_new0 (AsyncTextureLoadData, 1); + g_hash_table_insert (cache->priv->outstanding_requests, cache_key_dup (key), *request); + } + else + *request = pending; + + /* Regardless of whether there was a pending request, prepend our texture here. */ + (*request)->textures = g_slist_prepend ((*request)->textures, g_object_ref (*texture)); + + return had_pending; +} + +/** + * st_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 * +st_texture_cache_load_gicon (StTextureCache *cache, + GIcon *icon, + gint size) +{ + AsyncTextureLoadData *request; + ClutterActor *texture; + CacheKey key; + GtkIconTheme *theme; + GtkIconInfo *info; + + memset (&key, 0, sizeof(key)); + key.icon = icon; + key.size = size; + + if (create_texture_and_ensure_request (cache, &key, &request, &texture)) + return texture; + + /* 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) + { + /* hardcoded here for now; we should actually blow this away on + * icon theme changes probably */ + request->policy = ST_TEXTURE_CACHE_POLICY_FOREVER; + request->icon = g_object_ref (icon); + request->icon_info = info; + request->width = request->height = size; + + load_icon_pixbuf_async (cache, icon, info, size, NULL, on_pixbuf_loaded, request); + } + else + { + /* Blah; we failed to find the icon, but we've added our texture to the outstanding + * requests. In that case, just undo what create_texture_lookup_status did. + */ + g_slist_foreach (request->textures, (GFunc) g_object_unref, NULL); + g_slist_free (request->textures); + g_free (request); + g_hash_table_remove (cache->priv->outstanding_requests, &key); + } + + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_icon_name: + * @cache: The texture cache instance + * @name: Name of a themed icon + * @size: Size of themed + * + * Load a themed icon into a texture. + * + * Return Value: (transfer none): A new #ClutterTexture for the icon + */ +ClutterActor * +st_texture_cache_load_icon_name (StTextureCache *cache, + const char *name, + gint size) +{ + ClutterActor *texture; + GIcon *themed; + + themed = g_themed_icon_new (name); + texture = st_texture_cache_load_gicon (cache, themed, size); + g_object_unref (themed); + + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_uri_async: + * + * @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 * +st_texture_cache_load_uri_async (StTextureCache *cache, + const gchar *uri, + int available_width, + int available_height) +{ + ClutterTexture *texture; + AsyncTextureLoadData *data; + + texture = create_default_texture (cache); + + data = g_new0 (AsyncTextureLoadData, 1); + data->policy = ST_TEXTURE_CACHE_POLICY_NONE; + data->uri = g_strdup (uri); + data->width = available_width; + data->height = available_height; + data->textures = g_slist_prepend (data->textures, g_object_ref (texture)); + load_uri_pixbuf_async (cache, uri, available_width, available_height, NULL, on_pixbuf_loaded, data); + + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_uri_sync: + * + * @cache: The texture cache instance + * @policy: Requested lifecycle of cached data + * @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 * +st_texture_cache_load_uri_sync (StTextureCache *cache, + StTextureCachePolicy policy, + const gchar *uri, + int available_width, + int available_height, + GError **error) +{ + ClutterTexture *texture; + CoglHandle texdata; + GdkPixbuf *pixbuf; + CacheKey key; + + texture = create_default_texture (cache); + + memset (&key, 0, sizeof (CacheKey)); + key.policy = policy; + key.uri = (char*)uri; + key.size = available_width; + texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); + + if (texdata == NULL) + { + pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, 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); + + if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER) + { + g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata); + } + else + cogl_handle_unref (texdata); + } + else + set_texture_cogl_texture (texture, texdata); + + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_file_simple: + * @cache: A #StTextureCache + * @file_path: Filesystem path + * + * Synchronously load an image into a texture. The texture will be cached + * indefinitely. On error, this function returns an empty texture and prints a warning. + * + * Returns: (transfer none): A new #ClutterTexture + */ +ClutterActor * +st_texture_cache_load_file_simple (StTextureCache *cache, + const gchar *file_path) +{ + GFile *file; + char *uri; + ClutterActor *texture; + GError *error = NULL; + + file = g_file_new_for_path (file_path); + uri = g_file_get_uri (file); + + texture = st_texture_cache_load_uri_sync (cache, ST_TEXTURE_CACHE_POLICY_FOREVER, + uri, -1, -1, &error); + if (texture == NULL) + { + g_warning ("Failed to load %s: %s", file_path, error->message); + g_clear_error (&error); + texture = clutter_texture_new (); + } + return texture; +} + +/** + * st_texture_cache_load_from_data: + * @cache: The texture cache instance + * @data: Image data in PNG, GIF, etc format + * @len: length of @data + * @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 + * 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. + * + * Return value: (transfer none): A new #ClutterActor with the image data loaded if it was + * generated succesfully, %NULL otherwise + */ +ClutterActor * +st_texture_cache_load_from_data (StTextureCache *cache, + const guchar *data, + gsize len, + int size, + GError **error) +{ + ClutterTexture *texture; + CoglHandle texdata; + GdkPixbuf *pixbuf; + CacheKey key; + gchar *checksum; + + texture = create_default_texture (cache); + clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); + + 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) + { + 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); + } + + g_free (key.checksum); + + set_texture_cogl_texture (texture, texdata); + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_from_raw: + * @cache: a #StTextureCache + * @data: raw pixel data + * @len: the length of @data + * @has_alpha: whether @data includes an alpha channel + * @width: width in pixels of @data + * @height: width in pixels of @data + * @rowstride: rowstride of @data + * @size: size of icon to return + * + * Creates (or retrieves from cache) an icon based on raw pixel data. + * + * Return value: (transfer none): a new #ClutterActor displaying a + * pixbuf created from @data and the other parameters. + **/ +ClutterActor * +st_texture_cache_load_from_raw (StTextureCache *cache, + const guchar *data, + gsize len, + gboolean has_alpha, + int width, + int height, + int rowstride, + int size, + GError **error) +{ + ClutterTexture *texture; + CoglHandle texdata; + CacheKey key; + gchar *checksum; + + texture = create_default_texture (cache); + clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); + + /* In theory, two images of different size could have the same + * pixel data. We ignore that theory. + */ + 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) + { + texdata = cogl_texture_new_from_data (width, height, COGL_TEXTURE_NONE, + has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, + COGL_PIXEL_FORMAT_ANY, + rowstride, data); + g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata); + } + + g_free (key.checksum); + + set_texture_cogl_texture (texture, texdata); + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_load_thumbnail: + * @cache: + * @size: Size in pixels to use for thumbnail + * @uri: Source URI + * @mimetype: Source mime type + * + * Asynchronously load a thumbnail image of a URI into a texture. The + * returned texture object will be a new instance; however, its texture data + * may be shared with other objects. This implies the texture data is cached. + * + * The current caching policy is permanent; to uncache, you must explicitly + * call st_texture_cache_unref_thumbnail(). + * + * Returns: (transfer none): A new #ClutterActor + */ +ClutterActor * +st_texture_cache_load_thumbnail (StTextureCache *cache, + int size, + const char *uri, + const char *mimetype) +{ + ClutterTexture *texture; + AsyncTextureLoadData *data; + CacheKey key; + CoglHandle texdata; + + /* Don't attempt to load thumbnails for non-local URIs */ + if (!g_str_has_prefix (uri, "file://")) + { + GIcon *icon = icon_for_mimetype (mimetype); + return st_texture_cache_load_gicon (cache, icon, size); + } + + texture = create_default_texture (cache); + clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); + + memset (&key, 0, sizeof(key)); + key.size = size; + key.thumbnail_uri = (char*)uri; + + texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); + if (!texdata) + { + data = g_new0 (AsyncTextureLoadData, 1); + data->policy = ST_TEXTURE_CACHE_POLICY_FOREVER; + data->uri = g_strdup (uri); + data->mimetype = g_strdup (mimetype); + data->thumbnail = TRUE; + data->width = size; + data->height = size; + data->textures = g_slist_prepend (data->textures, g_object_ref (texture)); + load_thumbnail_async (cache, uri, mimetype, size, NULL, on_pixbuf_loaded, data); + } + else + { + set_texture_cogl_texture (texture, texdata); + } + + return CLUTTER_ACTOR (texture); +} + +static GIcon * +icon_for_recent (GtkRecentInfo *info) +{ + const char *mimetype; + + mimetype = gtk_recent_info_get_mime_type (info); + if (!mimetype) + { + return g_themed_icon_new (GTK_STOCK_FILE); + } + + return icon_for_mimetype (mimetype); +} + +/** + * st_texture_cache_load_recent_thumbnail: + * @cache: + * @size: Size in pixels to use for thumbnail + * @info: Recent item info + * + * Asynchronously load a thumbnail image of a #GtkRecentInfo into a texture. The + * returned texture object will be a new instance; however, its texture data + * may be shared with other objects. This implies the texture data is cached. + * + * The current caching policy is permanent; to uncache, you must explicitly + * call st_texture_cache_unref_recent_thumbnail(). + * + * Returns: (transfer none): A new #ClutterActor + */ +ClutterActor * +st_texture_cache_load_recent_thumbnail (StTextureCache *cache, + int size, + GtkRecentInfo *info) +{ + ClutterTexture *texture; + AsyncTextureLoadData *data; + CacheKey key; + CoglHandle texdata; + const char *uri; + + uri = gtk_recent_info_get_uri (info); + + /* Don't attempt to load thumbnails for non-local URIs */ + if (!g_str_has_prefix (uri, "file://")) + { + GIcon *icon = icon_for_recent (info); + return st_texture_cache_load_gicon (cache, icon, size); + } + + texture = CLUTTER_TEXTURE (clutter_texture_new ()); + clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size); + + memset (&key, 0, sizeof(key)); + key.size = size; + key.thumbnail_uri = (char*)gtk_recent_info_get_uri (info); + + texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key); + if (!texdata) + { + data = g_new0 (AsyncTextureLoadData, 1); + data->policy = ST_TEXTURE_CACHE_POLICY_FOREVER; + data->thumbnail = TRUE; + data->recent_info = gtk_recent_info_ref (info); + data->width = size; + data->height = size; + data->textures = g_slist_prepend (data->textures, g_object_ref (texture)); + load_recent_thumbnail_async (cache, info, size, NULL, on_pixbuf_loaded, data); + } + else + { + set_texture_cogl_texture (texture, texdata); + } + + return CLUTTER_ACTOR (texture); +} + +/** + * st_texture_cache_evict_thumbnail: + * @cache: + * @uri: Source URI + * + * Removes all references added by st_texture_cache_load_thumbnail() function + * created for the given URI. + */ +void +st_texture_cache_evict_thumbnail (StTextureCache *cache, + const char *uri) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, cache->priv->keyed_cache); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + CacheKey *cachekey = key; + + if (cachekey->thumbnail_uri == NULL || strcmp (cachekey->thumbnail_uri, uri) != 0) + continue; + + g_hash_table_iter_remove (&iter); + } +} + +/** + * st_texture_cache_evict_recent_thumbnail: + * @cache: + * @info: A recent info + * + * Removes all references added by st_texture_cache_load_recent_thumbnail() function + * for the URI associated with the given @info. + */ +void +st_texture_cache_evict_recent_thumbnail (StTextureCache *cache, + GtkRecentInfo *info) +{ + st_texture_cache_evict_thumbnail (cache, gtk_recent_info_get_uri (info)); +} + +static size_t +pixbuf_byte_size (GdkPixbuf *pixbuf) +{ + /* This bit translated from gtk+/gdk-pixbuf/gdk-pixbuf.c:gdk_pixbuf_copy. The comment + * there was: + * + * Calculate a semi-exact size. Here we copy with full rowstrides; + * maybe we should copy each row individually with the minimum + * rowstride? + */ + return (gdk_pixbuf_get_height (pixbuf) - 1) * gdk_pixbuf_get_rowstride (pixbuf) + + + gdk_pixbuf_get_width (pixbuf) * ((gdk_pixbuf_get_n_channels (pixbuf)* gdk_pixbuf_get_bits_per_sample (pixbuf) + 7) / 8); +} + +/** + * st_texture_cache_pixbuf_equal: + * + * Returns: %TRUE iff the given pixbufs are bytewise-equal + */ +gboolean +st_texture_cache_pixbuf_equal (StTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b) +{ + size_t size_a = pixbuf_byte_size (a); + size_t size_b = pixbuf_byte_size (b); + if (size_a != size_b) + return FALSE; + return memcmp (gdk_pixbuf_get_pixels (a), gdk_pixbuf_get_pixels (b), size_a) == 0; +} + +static StTextureCache *instance = NULL; + /** * st_texture_cache_get_default: * - * Returns the default texture cache. This is owned by St and should not be - * unreferenced or freed. - * - * Returns: (transfer none): a StTextureCache + * Return value: (transfer none): The global texture cache */ StTextureCache* st_texture_cache_get_default (void) { - if (G_UNLIKELY (__cache_singleton == NULL)) - __cache_singleton = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL); - - return __cache_singleton; -} - -#if 0 -static void -on_texure_finalized (gpointer data, - GObject *where_the_object_was) -{ - FinalizedClosure *closure = (FinalizedClosure *) data; - StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(closure->cache); - - g_hash_table_remove (priv->cache, closure->path); - - g_free(closure->path); - g_free(closure); -} -#endif - -/** - * st_texture_cache_get_size: - * @self: A #StTextureCache - * - * Returns the number of items in the texture cache - * - * Returns: the current size of the cache - */ -gint -st_texture_cache_get_size (StTextureCache *self) -{ - StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); - - return g_hash_table_size (priv->cache); -} - -static void -add_texture_to_cache (StTextureCache *self, - const gchar *path, - StTextureCacheItem *item) -{ - /* FinalizedClosure *closure; */ - StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); - - g_hash_table_insert (priv->cache, g_strdup (path), item); - -#if 0 - /* Make sure we can remove from hash */ - closure = g_new0 (FinalizedClosure, 1); - closure->path = g_strdup (path); - closure->cache = self; - - g_object_weak_ref (G_OBJECT (res), on_texure_finalized, closure); -#endif -} - -/* NOTE: you should unref the returned texture when not needed */ - -/** - * st_texture_cache_get_texture: - * @self: A #StTextureCache - * @path: A path to a image file - * - * Create a new ClutterTexture with the specified image. Adds the image to the - * cache if the image had not been previously loaded. Subsequent calls with - * the same image path will return a new ClutterTexture with the previously - * loaded image. - * - * Returns: (transfer none): a newly created ClutterTexture - */ -ClutterTexture* -st_texture_cache_get_texture (StTextureCache *self, - const gchar *path) -{ - ClutterActor *texture; - CoglHandle *handle; - StTextureCachePrivate *priv; - StTextureCacheItem *item; - - g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL); - g_return_val_if_fail (path != NULL, NULL); - - - priv = TEXTURE_CACHE_PRIVATE (self); - - item = g_hash_table_lookup (priv->cache, path); - - if (item && item->posX != -1) - { - GError *err = NULL; - /* - * We have a cache hit, but it's for a partial texture. The only - * sane option is to read it from disk and just don't cache it - * at all. - */ - return CLUTTER_TEXTURE(clutter_texture_new_from_file(path, &err)); - } - if (!item) - { - GError *err = NULL; - - item = st_texture_cache_item_new (); - item->posX = -1; - item->posY = -1; - item->ptr = clutter_texture_new_from_file (path, &err); - clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr), - &item->width, &item->height); - - if (!item->ptr) - { - if (err) - { - g_warning ("Error loading image: %s", err->message); - g_error_free (err); - } - - return NULL; - } - - add_texture_to_cache (self, path, item); - } - - texture = clutter_texture_new (); - handle = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (item->ptr)); - clutter_texture_set_cogl_texture ((ClutterTexture*) texture, handle); - - return (ClutterTexture*) texture; -} - - -/** - * st_texture_cache_get_actor: - * @self: A #StTextureCache - * @path: A path to a image file - * - * Create a new ClutterSubTexture with the specified image. Adds the image to the - * cache if the image had not been previously loaded. Subsequent calls with - * the same image path will return a new ClutterTexture with the previously - * loaded image. - * - * Use this function if all you need is an actor for drawing. - * - * Returns: (transfer none): a newly created ClutterTexture - */ -ClutterActor* -st_texture_cache_get_actor (StTextureCache *self, - const gchar *path) -{ - StTextureCachePrivate *priv; - StTextureCacheItem *item; - GError *err = NULL; - - g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL); - g_return_val_if_fail (path != NULL, NULL); - - priv = TEXTURE_CACHE_PRIVATE (self); - - - item = g_hash_table_lookup (priv->cache, path); - - if (item) - { - int posX = item->posX; - int posY = item->posY; - if (posX == -1) - posX = 0; - if (posY == -1) - posY = 0; - return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), posX, posY, - item->width, item->height); - } - - item = st_texture_cache_item_new (); - item->posX = -1; - item->posY = -1; - item->ptr = clutter_texture_new_from_file (path, &err); - clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr), - &item->width, &item->height); - - if (!item->ptr) - { - if (err) - { - g_warning ("Error loading image: %s", err->message); - g_error_free (err); - } - - return NULL; - } - - add_texture_to_cache (self, path, item); - - return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), 0, 0, item->width, - item->height); -} - -void -st_texture_cache_load_cache (StTextureCache *self, - const gchar *filename) -{ - FILE *file; - StTextureCacheItem *element, head; - int ret; - ClutterActor *actor; - GError *error = NULL; - StTextureCachePrivate *priv; - - g_return_if_fail (ST_IS_TEXTURE_CACHE (self)); - g_return_if_fail (filename != NULL); - - priv = TEXTURE_CACHE_PRIVATE (self); - - file = fopen(filename, "rm"); - if (!file) - return; - - ret = fread (&head, sizeof(StTextureCacheItem), 1, file); - if (ret < 0) - { - fclose (file); - return; - } - - /* check if we already if this texture in the cache */ - if (g_hash_table_lookup (priv->cache, head.filename)) - { - /* skip it, we're done */ - fclose (file); - return; - } - - actor = clutter_texture_new_from_file (head.filename, &error); - - if (error) - { - g_critical (G_STRLOC ": Error opening cache image file: %s", - error->message); - g_clear_error (&error); - fclose (file); - return; - } - - element = st_texture_cache_item_new (); - element->posX = -1; - element->posY = -1; - element->ptr = actor; - strncpy (element->filename, head.filename, 256); - clutter_texture_get_base_size (CLUTTER_TEXTURE (element->ptr), - &element->width, &element->height); - g_hash_table_insert (priv->cache, element->filename, element); - - while (!feof (file)) - { - element = st_texture_cache_item_new (); - ret = fread (element, sizeof (StTextureCacheItem), 1, file); - if (ret < 1) - { - /* end of file */ - st_texture_cache_item_free (element); - break; - } - - element->ptr = actor; - - if (g_hash_table_lookup (priv->cache, element->filename)) - { - /* file is already in the cache.... */ - st_texture_cache_item_free (element); - } else { - g_hash_table_insert (priv->cache, element->filename, element); - } - } + if (instance == NULL) + instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL); + return instance; } diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h index ea299a035..0c51a38b8 100644 --- a/src/st/st-texture-cache.h +++ b/src/st/st-texture-cache.h @@ -1,96 +1,103 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -/* - * st-texture-cache.h: Cached textures object - * - * Copyright 2007 OpenedHand - * Copyright 2009 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 2.1, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - * Boston, MA 02111-1307, USA. - * - */ +#ifndef __ST_TEXTURE_CACHE_H__ +#define __ST_TEXTURE_CACHE_H__ -#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) -#error "Only can be included directly.h" -#endif - -#ifndef _ST_TEXTURE_CACHE -#define _ST_TEXTURE_CACHE - -#include +#include +#include +#include #include -G_BEGIN_DECLS +#define ST_TYPE_TEXTURE_CACHE (st_texture_cache_get_type ()) +#define ST_TEXTURE_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_TEXTURE_CACHE, StTextureCache)) +#define ST_TEXTURE_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) +#define ST_IS_TEXTURE_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_TEXTURE_CACHE)) +#define ST_IS_TEXTURE_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_TEXTURE_CACHE)) +#define ST_TEXTURE_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) -#define ST_TYPE_TEXTURE_CACHE st_texture_cache_get_type() +typedef struct _StTextureCache StTextureCache; +typedef struct _StTextureCacheClass StTextureCacheClass; -#define ST_TEXTURE_CACHE(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - ST_TYPE_TEXTURE_CACHE, StTextureCache)) +typedef struct _StTextureCachePrivate StTextureCachePrivate; -#define ST_TEXTURE_CACHE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) +struct _StTextureCache +{ + GObject parent; -#define ST_IS_TEXTURE_CACHE(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - ST_TYPE_TEXTURE_CACHE)) + StTextureCachePrivate *priv; +}; -#define ST_IS_TEXTURE_CACHE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - ST_TYPE_TEXTURE_CACHE)) - -#define ST_TEXTURE_CACHE_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) - -/** - * StTextureCache: - * - * The contents of this structure are private and should only be accessed - * through the public API. - */ -typedef struct { - /*< private >*/ - GObject parent; -} StTextureCache; - -typedef struct { +struct _StTextureCacheClass +{ GObjectClass parent_class; - void (* loaded) (StTextureCache *self, - const gchar *path, - ClutterTexture *texture); +}; - void (* error_loading) (StTextureCache *self, - GError *error); -} StTextureCacheClass; +typedef enum { + ST_TEXTURE_CACHE_POLICY_NONE, + ST_TEXTURE_CACHE_POLICY_FOREVER +} StTextureCachePolicy; -GType st_texture_cache_get_type (void); +GType st_texture_cache_get_type (void) G_GNUC_CONST; StTextureCache* st_texture_cache_get_default (void); -ClutterTexture* st_texture_cache_get_texture (StTextureCache *self, - const gchar *path); -ClutterActor* st_texture_cache_get_actor (StTextureCache *self, - const gchar *path); +ClutterActor *st_texture_cache_bind_pixbuf_property (StTextureCache *cache, + GObject *object, + const char *property_name); -gint st_texture_cache_get_size (StTextureCache *self); +ClutterActor *st_texture_cache_load_icon_name (StTextureCache *cache, + const char *name, + gint size); -void st_texture_cache_load_cache (StTextureCache *self, - const char *filename); +ClutterActor *st_texture_cache_load_gicon (StTextureCache *cache, + GIcon *icon, + gint size); -G_END_DECLS +ClutterActor *st_texture_cache_load_thumbnail (StTextureCache *cache, + int size, + const char *uri, + const char *mimetype); -#endif /* _ST_TEXTURE_CACHE */ +ClutterActor *st_texture_cache_load_recent_thumbnail (StTextureCache *cache, + int size, + GtkRecentInfo *info); + +void st_texture_cache_evict_thumbnail (StTextureCache *cache, + const char *uri); + +void st_texture_cache_evict_recent_thumbnail (StTextureCache *cache, + GtkRecentInfo *info); + +ClutterActor *st_texture_cache_load_uri_async (StTextureCache *cache, + const gchar *filename, + int available_width, + int available_height); + +ClutterActor *st_texture_cache_load_uri_sync (StTextureCache *cache, + StTextureCachePolicy policy, + const gchar *filename, + int available_width, + int available_height, + GError **error); + +ClutterActor *st_texture_cache_load_file_simple (StTextureCache *cache, + const gchar *file_path); + +ClutterActor *st_texture_cache_load_from_data (StTextureCache *cache, + const guchar *data, + gsize len, + int size, + GError **error); +ClutterActor *st_texture_cache_load_from_raw (StTextureCache *cache, + const guchar *data, + gsize len, + gboolean has_alpha, + int width, + int height, + int rowstride, + int size, + GError **error); + +gboolean st_texture_cache_pixbuf_equal (StTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b); + +#endif /* __ST_TEXTURE_CACHE_H__ */ diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 294fc01bb..e5d200c1a 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -905,8 +905,8 @@ st_widget_real_style_changed (StWidget *self) /* `border-image' takes precedence over `background-image'. * Firefox lets the background-image shine thru when border-image has * alpha an channel, maybe that would be an option for the future. */ - texture = st_texture_cache_get_texture (texture_cache, - filename); + texture = (ClutterTexture*) st_texture_cache_load_file_simple (texture_cache, + filename); clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), &width, &height); @@ -968,16 +968,8 @@ st_widget_real_style_changed (StWidget *self) bg_file = st_theme_node_get_background_image (theme_node); if (bg_file != NULL) { - texture = st_texture_cache_get_texture (texture_cache, bg_file); - priv->background_image = (ClutterActor*) texture; - - if (priv->background_image != NULL) - { - clutter_actor_set_parent (priv->background_image, - CLUTTER_ACTOR (self)); - } - else - g_warning ("Could not load %s", bg_file); + priv->background_image = st_texture_cache_load_file_simple (texture_cache, bg_file); + clutter_actor_set_parent (priv->background_image, CLUTTER_ACTOR (self)); has_changed = TRUE; relayout_needed = TRUE;