diff options
-rw-r--r-- | ChangeLog | 11 | ||||
-rw-r--r-- | libnautilus-private/nautilus-icon-container.c | 104 | ||||
-rw-r--r-- | libnautilus-private/nautilus-icon-factory.c | 219 | ||||
-rw-r--r-- | libnautilus-private/nautilus-icon-factory.h | 10 | ||||
-rw-r--r-- | libnautilus-private/nautilus-thumbnails.c | 160 | ||||
-rw-r--r-- | libnautilus-private/nautilus-thumbnails.h | 17 |
6 files changed, 464 insertions, 57 deletions
@@ -1,5 +1,16 @@ 2007-09-10 Alexander Larsson <alexl@redhat.com> + * libnautilus-private/nautilus-thumbnails.[ch]: + Add nautilus_thumbnail_load_image_async and cancel. + + * libnautilus-private/nautilus-icon-container.c: + * libnautilus-private/nautilus-icon-factory.[ch]: + Load thumbnails asynchronously + + Patch from Christian Neumair + +2007-09-10 Alexander Larsson <alexl@redhat.com> + * libnautilus-private/nautilus-dnd.h: * libnautilus-private/nautilus-file-dnd.c: * libnautilus-private/nautilus-icon-dnd.c: diff --git a/libnautilus-private/nautilus-icon-container.c b/libnautilus-private/nautilus-icon-container.c index eb160e1bf..1c25745c6 100644 --- a/libnautilus-private/nautilus-icon-container.c +++ b/libnautilus-private/nautilus-icon-container.c @@ -5678,6 +5678,90 @@ handle_vadjustment_changed (GtkAdjustment *adjustment, nautilus_icon_container_update_visible_icons (container); } +/* + * used to resize ICON_NAME_THUMBNAIL_LOADING to the expected thumbnail size. + */ +static void +sanitize_loading_thumbnail_image_size (NautilusIconContainer *container, + const char *mime_type, + GdkPixbuf **image, + NautilusEmblemAttachPoints *attach_points, + GdkRectangle *embedded_text_rect) +{ + NautilusIconContainerDetails *details; + double pixels_per_unit; + + details = container->details; + pixels_per_unit = (double) nautilus_get_icon_size_for_zoom_level (container->details->zoom_level) + / NAUTILUS_ICON_SIZE_STANDARD; + + if (gdk_pixbuf_get_width (*image) < NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit && + gdk_pixbuf_get_height (*image) < NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit) { + /* TODO? this only handles icons smaller than the expected thumbnail size ATM. + * Should not be a common problem, though */ + GdkPixbuf *new_image; + double x_size; + double y_size; + double x_offset; + double y_offset; + int i; + + if (g_str_has_prefix (mime_type, "video/")) { + /* assume 4:3 aspect ratio for videos i.e. we'll always occupy the full width. */ + x_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit; + y_size = 3./4 * x_size; + } else { + /* scale up to the max. thumbnail size. + * This is correct at least in one dimension, and prevents the icons from jumping + * around as the thumbnail is created, if it is tall for text below icon, and if it + * is wide for text beside icon. + */ + x_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit; + y_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit; + } + + /* maybe the estimated size was smaller than the input pixbuf, so size the surrounding + * image up. This only seems to be relevant in the 4:3 case, for y_size. + */ + x_size = MAX (x_size, gdk_pixbuf_get_width (*image)); + y_size = MAX (y_size, gdk_pixbuf_get_height (*image)); + + x_offset = x_size - gdk_pixbuf_get_width (*image); + y_offset = y_size - gdk_pixbuf_get_height (*image); + + /* center wrt "minor" dimension, i.e. horizontally for text below + * and vertically for text besides icon */ + if (details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) + y_offset /= 2; + else + x_offset /= 2; + + new_image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, + gdk_pixbuf_get_bits_per_sample (*image), + x_size, y_size); + + gdk_pixbuf_fill (new_image, 0x00000000); + gdk_pixbuf_copy_area (*image, + 0, 0, + gdk_pixbuf_get_width (*image), + gdk_pixbuf_get_height (*image), + new_image, + x_offset, y_offset); + + g_object_unref (*image); + *image = new_image; + + for (i = 0; i < attach_points->num_points; i++) { + attach_points->points[i].x += x_offset; + attach_points->points[i].y += y_offset; + } + + embedded_text_rect->x += x_offset; + embedded_text_rect->y += y_offset; + } +} + + void nautilus_icon_container_update_icon (NautilusIconContainer *container, NautilusIcon *icon) @@ -5732,15 +5816,14 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, modifier = "accept"; } - pixbuf = nautilus_icon_factory_get_pixbuf_for_icon - (icon_name, + pixbuf = nautilus_icon_factory_get_pixbuf_for_file_with_icon + ((NautilusFile *) icon->data, + icon_name, modifier, icon_size, &attach_points, &embedded_text_rect, FALSE, TRUE, NULL); - - g_free (icon_name); if (embedded_text_rect.width > MINIMUM_EMBEDDED_TEXT_RECT_WIDTH && embedded_text_rect.height > MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT && @@ -5794,6 +5877,17 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, "additional_text", additional_text, "highlighted_for_drop", icon == details->drop_target, NULL); + + if (nautilus_file_is_thumbnailing ((NautilusFile *) icon->data)) { + char* mime_type; + mime_type = nautilus_file_get_mime_type ((NautilusFile *)icon->data); + sanitize_loading_thumbnail_image_size (container, + mime_type, + &pixbuf, + &attach_points, + &embedded_text_rect); + g_free (mime_type); + } nautilus_icon_canvas_item_set_image (icon->item, pixbuf); nautilus_icon_canvas_item_set_attach_points (icon->item, &attach_points); @@ -5807,6 +5901,8 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, g_free (editable_text); g_free (additional_text); + + g_free (icon_name); } static gboolean diff --git a/libnautilus-private/nautilus-icon-factory.c b/libnautilus-private/nautilus-icon-factory.c index df071b2cf..4f30bbe1a 100644 --- a/libnautilus-private/nautilus-icon-factory.c +++ b/libnautilus-private/nautilus-icon-factory.c @@ -29,6 +29,7 @@ #include "nautilus-icon-factory.h" #include "nautilus-default-file-icon.h" +#include "nautilus-directory-notify.h" #include "nautilus-file-attributes.h" #include "nautilus-file-private.h" #include "nautilus-file-utilities.h" @@ -154,6 +155,7 @@ typedef struct { CacheIcon *fallback_icon; GHashTable *image_mime_types; + GList *async_thumbnail_load_handles; } NautilusIconFactory; #define NAUTILUS_ICON_FACTORY(obj) \ @@ -347,6 +349,64 @@ load_thumbnail_frame (NautilusIconFactory *factory) g_free (image_path); } +typedef struct { + NautilusFile *file; + char *modifier; + guint nominal_size; + gboolean force_nominal; +} AsnycThumbnailLoadFuncData; + +static void +async_thumbnail_load_func (NautilusThumbnailAsyncLoadHandle *handle, + const char *path, + GdkPixbuf *pixbuf, + double scale_x, + double scale_y, + gpointer user_data) +{ + NautilusIconFactory *factory; + GHashTable *hash_table; + CacheKey *key; + CacheIcon *cached_icon; + struct stat statbuf; + AsnycThumbnailLoadFuncData *data = user_data; + + factory = get_icon_factory (); + hash_table = factory->icon_cache; + + nautilus_file_set_is_thumbnailing (data->file, FALSE); + factory->async_thumbnail_load_handles = + g_list_remove (factory->async_thumbnail_load_handles, handle); + + if (stat (path, &statbuf) != 0 || + !S_ISREG (statbuf.st_mode)) { + g_message ("NautilusIconFactory: Failed to determine mtime for %s. Aborting thumbnailing request.", path); + goto out; + } + + cached_icon = cache_icon_new (pixbuf, NULL, scale_x, scale_y); + cached_icon->mtime = statbuf.st_mtime; + + if (cached_icon != NULL) { + key = g_new (CacheKey, 1); + key->name = g_strdup (path); + key->modifier = g_strdup (data->modifier); + key->nominal_size = data->nominal_size; + key->force_nominal = data->force_nominal; + + g_hash_table_insert (hash_table, key, cached_icon); + + nautilus_file_changed (data->file); + } + +out: + nautilus_file_unref (data->file); + g_free (data->modifier); + g_free (data); +} + + + static void nautilus_icon_factory_instance_init (NautilusIconFactory *factory) { @@ -685,12 +745,23 @@ nautilus_icon_factory_clear (void) } static void +cancel_thumbnail_read_foreach (gpointer data, + gpointer user_data) +{ + NautilusThumbnailAsyncLoadHandle *handle = data; + nautilus_thumbnail_load_image_cancel (handle); +} + +static void nautilus_icon_factory_finalize (GObject *object) { NautilusIconFactory *factory; factory = NAUTILUS_ICON_FACTORY (object); + g_list_foreach (factory->async_thumbnail_load_handles, cancel_thumbnail_read_foreach, NULL); + g_list_free (factory->async_thumbnail_load_handles); + if (factory->icon_cache) { g_hash_table_destroy (factory->icon_cache); factory->icon_cache = NULL; @@ -1300,6 +1371,38 @@ create_normal_cache_icon (const char *icon, return cache_icon; } +static CacheIcon * +lookup_icon_from_cache (const char *icon, + const char *modifier, + guint nominal_size, + gboolean force_nominal) +{ + NautilusIconFactory *factory; + GHashTable *hash_table; + CacheKey lookup_key, *key; + CacheIcon *value; + + lookup_key.name = (char *)icon; + lookup_key.modifier = (char *)modifier; + lookup_key.nominal_size = nominal_size; + lookup_key.force_nominal = force_nominal; + + factory = get_icon_factory (); + hash_table = factory->icon_cache; + + if (g_hash_table_lookup_extended (hash_table, &lookup_key, + (gpointer *) &key, (gpointer *) &value)) { + /* Found it in the table. */ + g_assert (key != NULL); + g_assert (value != NULL); + } else { + key = NULL; + value = NULL; + } + + return value; +} + /* Get the icon, handling the caching. * If @picky is true, then only an unscaled icon is acceptable. @@ -1316,34 +1419,17 @@ get_icon_from_cache (const char *icon, { NautilusIconFactory *factory; GHashTable *hash_table; - CacheKey lookup_key; CacheKey *key; CacheIcon *cached_icon; - gpointer key_in_table, value; struct stat statbuf; g_return_val_if_fail (icon != NULL, NULL); - - key = NULL; - cached_icon = NULL; factory = get_icon_factory (); hash_table = factory->icon_cache; /* Check to see if it's already in the table. */ - lookup_key.name = (char *)icon; - lookup_key.modifier = (char *)modifier; - lookup_key.nominal_size = nominal_size; - lookup_key.force_nominal = force_nominal; - - if (g_hash_table_lookup_extended (hash_table, &lookup_key, - &key_in_table, &value)) { - /* Found it in the table. */ - g_assert (key_in_table != NULL); - g_assert (value != NULL); - key = key_in_table; - cached_icon = value; - } + cached_icon = lookup_icon_from_cache (icon, modifier, nominal_size, force_nominal); /* Make sure that thumbnails and image-as-itself icons gets reloaded when they change: */ @@ -1547,10 +1633,85 @@ nautilus_get_relative_icon_size_for_zoom_level (NautilusZoomLevel zoom_level) return (float)nautilus_get_icon_size_for_zoom_level (zoom_level) / NAUTILUS_ICON_SIZE_STANDARD; } - - /* Convenience cover for nautilus_icon_factory_get_icon_for_file * and nautilus_icon_factory_get_pixbuf_for_icon. + * + * If a file has an associated thumbnail, the thumb is loaded asynchronously, + * a loading thumbnail image is returned + * and the file will receive a "changed" event once the thumbnail has been loaded. + * + * The "file" parameter is only used for thumbnailing, + * for the file change notification once the actual thumbnail + * has been loaded. + */ +GdkPixbuf * +nautilus_icon_factory_get_pixbuf_for_file_with_icon (NautilusFile *file, + const char *icon, + const char *modifier, + guint size_in_pixels, + NautilusEmblemAttachPoints *attach_points, + GdkRectangle *embedded_text_rect, + gboolean force_size, + gboolean wants_default, + char **display_name) +{ + GdkPixbuf *pixbuf; + NautilusIconFactory *factory; + gboolean is_thumbnail; + + factory = get_icon_factory (); + + is_thumbnail = strstr (icon, "/.thumbnails/") != NULL; + + if (is_thumbnail && + !lookup_icon_from_cache (icon, modifier, size_in_pixels, force_size)) { + AsnycThumbnailLoadFuncData *data; + + /* Asynchronous thumbnail loading. + * + * This heavily improves performance for folders containing lots of + * previously thumbnailed files. + * + * Note: We do not pass the additional thumbnail parameters (attach points etc.) + * to the thread as we don't need them for the cache. The API user may herself + * re-request the loaded thumbnail with the correct parameters, which will be set + * accordingly in nautilus_icon_factory_get_pixbuf_for_icon() on cache hit + * once it is filled. + */ + + data = g_new (AsnycThumbnailLoadFuncData, 1); + data->file = nautilus_file_ref (file); + data->modifier = g_strdup (modifier); + data->nominal_size = size_in_pixels; + data->force_nominal = force_size; + + nautilus_file_set_is_thumbnailing (file, TRUE); + + factory->async_thumbnail_load_handles = g_list_prepend ( + factory->async_thumbnail_load_handles, + nautilus_thumbnail_load_image_async (icon, + 0, /* base_size */ + size_in_pixels, + force_size, + async_thumbnail_load_func, + data)); + + icon = ICON_NAME_THUMBNAIL_LOADING; + } + + + pixbuf = nautilus_icon_factory_get_pixbuf_for_icon (icon, + modifier, size_in_pixels, + attach_points, embedded_text_rect, + force_size, + wants_default, display_name); + + return pixbuf; +} + +/* + * like nautilus_icon_factory_get_pixbuf_for_file_with_icon() but does the icon lookup itself, + * doesn't allow emblem and text rect fetching. */ GdkPixbuf * nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file, @@ -1558,9 +1719,11 @@ nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file, guint size_in_pixels, gboolean force_size) { - char *icon; GdkPixbuf *pixbuf; + NautilusIconFactory *factory; + char *icon; + factory = get_icon_factory (); /* Get the pixbuf for this file. */ icon = nautilus_icon_factory_get_icon_for_file (file, FALSE); @@ -1568,12 +1731,12 @@ nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file, return NULL; } - pixbuf = nautilus_icon_factory_get_pixbuf_for_icon (icon, modifier, - size_in_pixels, - NULL, NULL, - force_size, - TRUE, NULL); - + pixbuf = nautilus_icon_factory_get_pixbuf_for_file_with_icon (file, + icon, modifier, + size_in_pixels, + NULL, NULL, + force_size, + TRUE, NULL); g_free (icon); return pixbuf; @@ -1586,7 +1749,7 @@ nautilus_icon_factory_get_pixbuf_for_file_with_stock_size (NautilusFile *file, { return nautilus_icon_factory_get_pixbuf_for_file (file, modifier, gtk_icon_size_to_nominal_size (stock_size), - TRUE); /* force_size */ + TRUE /* force_size */); } diff --git a/libnautilus-private/nautilus-icon-factory.h b/libnautilus-private/nautilus-icon-factory.h index c56d36744..673172125 100644 --- a/libnautilus-private/nautilus-icon-factory.h +++ b/libnautilus-private/nautilus-icon-factory.h @@ -151,6 +151,16 @@ GdkPixbuf *nautilus_icon_factory_get_pixbuf_for_file_with_stock_size (NautilusFi const char *modifier, GtkIconSize stock_size); +GdkPixbuf * nautilus_icon_factory_get_pixbuf_for_file_with_icon (NautilusFile *file, + const char *icon, + const char *modifier, + guint size_in_pixels, + NautilusEmblemAttachPoints *attach_points, + GdkRectangle *embedded_text_rect, + gboolean force_size, + gboolean wants_default, + char **display_name); + /* Convenience routine for getting a pixbuf from an icon name */ diff --git a/libnautilus-private/nautilus-thumbnails.c b/libnautilus-private/nautilus-thumbnails.c index 380391544..91f79718f 100644 --- a/libnautilus-private/nautilus-thumbnails.c +++ b/libnautilus-private/nautilus-thumbnails.c @@ -69,6 +69,16 @@ typedef struct { time_t original_file_mtime; } NautilusThumbnailInfo; +struct NautilusThumbnailAsyncLoadHandle { + EelReadFileHandle *eel_read_handle; + char *file_path; + guint base_size; + guint nominal_size; + gboolean force_nominal; + NautilusThumbnailAsyncLoadFunc load_func; + gpointer load_func_user_data; +}; + /* * Thumbnail thread state. @@ -330,39 +340,26 @@ thumbnail_loader_area_prepared (GdkPixbufLoader *loader, *args->scale_y_out = (double) gdk_pixbuf_get_height (pixbuf) / args->original_height; } -/* routine to load an image from the passed-in path - */ -GdkPixbuf * -nautilus_thumbnail_load_image (const char *path, - guint base_size, - guint nominal_size, - gboolean force_nominal, - double *scale_x_out, - double *scale_y_out) +static GdkPixbuf * +get_pixbuf_from_data (const unsigned char *buffer, + gsize buflen, + const char *path, + guint base_size, + guint nominal_size, + gboolean force_nominal, + double *scale_x_out, + double *scale_y_out) { - guchar *buffer; GdkPixbufLoader *loader; GdkPixbuf *pixbuf; - GError *error; - gsize buflen; ThumbnailLoadArgs args; - - error = NULL; + GError *error; if (thumbnail_icon_size == 0) { eel_preferences_add_auto_integer (NAUTILUS_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE, &thumbnail_icon_size); } - - if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error)) { - g_message ("Failed to load %s into memory: %s", path, error->message); - - g_error_free (error); - - return NULL; - } - loader = gdk_pixbuf_loader_new (); g_signal_connect (loader, "size-prepared", G_CALLBACK (thumbnail_loader_size_prepared), @@ -378,17 +375,20 @@ nautilus_thumbnail_load_image (const char *path, args.scale_x_out = scale_x_out; args.scale_y_out = scale_y_out; + error = NULL; + if (!gdk_pixbuf_loader_write (loader, buffer, buflen, &error)) { g_message ("Failed to write %s to thumbnail pixbuf loader: %s", path, error->message); gdk_pixbuf_loader_close (loader, NULL); g_object_unref (G_OBJECT (loader)); g_error_free (error); - g_free (buffer); return NULL; } + error = NULL; + if (!gdk_pixbuf_loader_close (loader, &error) || /* Seems we have to check this even if it returned TRUE (#403255) */ error != NULL) { @@ -396,7 +396,6 @@ nautilus_thumbnail_load_image (const char *path, g_object_unref (G_OBJECT (loader)); g_error_free (error); - g_free (buffer); return NULL; } @@ -404,11 +403,122 @@ nautilus_thumbnail_load_image (const char *path, pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)); g_object_unref (G_OBJECT (loader)); + + return pixbuf; +} + + +/* routine to load an image from the passed-in path + */ +GdkPixbuf * +nautilus_thumbnail_load_image (const char *path, + guint base_size, + guint nominal_size, + gboolean force_nominal, + double *scale_x_out, + double *scale_y_out) +{ + GdkPixbuf *pixbuf; + guchar *buffer; + gsize buflen; + GError *error; + + error = NULL; + + if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error)) { + g_message ("Failed to load %s into memory: %s", path, error->message); + + g_error_free (error); + + return NULL; + } + + pixbuf = get_pixbuf_from_data (buffer, buflen, path, + base_size, nominal_size, force_nominal, + scale_x_out, scale_y_out); + g_free (buffer); return pixbuf; } +static void +async_thumbnail_read_image (GnomeVFSResult result, + GnomeVFSFileSize file_size, + char *file_contents, + gpointer callback_data) +{ + GdkPixbuf *pixbuf; + double scale_x, scale_y; + + NautilusThumbnailAsyncLoadHandle *handle = callback_data; + + pixbuf = NULL; + scale_x = scale_y = 1.0; + + if (result == GNOME_VFS_OK) { + pixbuf = get_pixbuf_from_data (file_contents, file_size, + handle->file_path, + handle->base_size, + handle->nominal_size, + handle->force_nominal, + &scale_x, &scale_y); + } + + handle->load_func (handle, + handle->file_path, + pixbuf, scale_x, scale_y, + handle->load_func_user_data); + + gdk_pixbuf_unref (pixbuf); + + g_free (handle->file_path); + g_free (handle); +} + +NautilusThumbnailAsyncLoadHandle * +nautilus_thumbnail_load_image_async (const char *path, + guint base_size, + guint nominal_size, + gboolean force_nominal, + NautilusThumbnailAsyncLoadFunc load_func, + gpointer load_func_user_data) +{ + NautilusThumbnailAsyncLoadHandle *handle; + char *uri; + + uri = gnome_vfs_get_uri_from_local_path (path); + if (uri == NULL) { + return NULL; + } + + handle = g_new (NautilusThumbnailAsyncLoadHandle, 1); + handle->eel_read_handle = + eel_read_entire_file_async (uri, GNOME_VFS_PRIORITY_DEFAULT, + (EelReadFileCallback) async_thumbnail_read_image, + handle); + handle->file_path = g_strdup (path); + handle->base_size = base_size; + handle->nominal_size = nominal_size; + handle->force_nominal = force_nominal; + handle->load_func = load_func; + handle->load_func_user_data = load_func_user_data; + + g_free (uri); + + return handle; +} + +void +nautilus_thumbnail_load_image_cancel (NautilusThumbnailAsyncLoadHandle *handle) +{ + g_assert (handle != NULL); + + eel_read_file_cancel (handle->eel_read_handle); + g_free (handle->file_path); + g_free (handle); +} + void nautilus_thumbnail_remove_from_queue (const char *file_uri) { diff --git a/libnautilus-private/nautilus-thumbnails.h b/libnautilus-private/nautilus-thumbnails.h index c62e3bd76..6e463b59e 100644 --- a/libnautilus-private/nautilus-thumbnails.h +++ b/libnautilus-private/nautilus-thumbnails.h @@ -28,6 +28,15 @@ #include <gdk-pixbuf/gdk-pixbuf.h> #include <libnautilus-private/nautilus-file.h> +typedef struct NautilusThumbnailAsyncLoadHandle NautilusThumbnailAsyncLoadHandle; + +typedef void (* NautilusThumbnailAsyncLoadFunc) (NautilusThumbnailAsyncLoadHandle *handle, + const char *path, + GdkPixbuf *pixbuf, + double scale_x, + double scale_y, + gpointer user_data); + /* Returns NULL if there's no thumbnail yet. */ void nautilus_create_thumbnail (NautilusFile *file); void nautilus_thumbnail_frame_image (GdkPixbuf **pixbuf); @@ -37,6 +46,14 @@ GdkPixbuf *nautilus_thumbnail_load_image (const char *path, gboolean force_nominal, double *scale_x_out, double *scale_y_out); +NautilusThumbnailAsyncLoadHandle * + nautilus_thumbnail_load_image_async (const char *path, + guint base_size, + guint nominal_size, + gboolean force_nominal, + NautilusThumbnailAsyncLoadFunc load_func, + gpointer load_func_user_data); +void nautilus_thumbnail_load_image_cancel (NautilusThumbnailAsyncLoadHandle *handle); void nautilus_update_thumbnail_file_copied (const char *source_file_uri, const char *destination_file_uri); void nautilus_update_thumbnail_file_renamed (const char *source_file_uri, |