diff options
Diffstat (limited to 'libnautilus/nautilus-icon-factory.c')
-rw-r--r-- | libnautilus/nautilus-icon-factory.c | 855 |
1 files changed, 515 insertions, 340 deletions
diff --git a/libnautilus/nautilus-icon-factory.c b/libnautilus/nautilus-icon-factory.c index bf86db7bf..08045d1af 100644 --- a/libnautilus/nautilus-icon-factory.c +++ b/libnautilus/nautilus-icon-factory.c @@ -35,88 +35,125 @@ #include "nautilus-string.h" #include "nautilus-default-file-icon.h" +#include "nautilus-metadata.h" + +#define ICON_NAME_DIRECTORY "i-directory.png" +#define ICON_NAME_DIRECTORY_CLOSED "i-dirclosed.png" +#define ICON_NAME_EXECUTABLE "i-executable.png" +#define ICON_NAME_REGULAR "i-regular.png" +#define ICON_NAME_CORE "i-core.png" +#define ICON_NAME_SOCKET "i-sock.png" +#define ICON_NAME_FIFO "i-fifo.png" +#define ICON_NAME_CHARACTER_DEVICE "i-chardev.png" +#define ICON_NAME_BLOCK_DEVICE "i-blockdev.png" +#define ICON_NAME_BROKEN_SYMBOLIC_LINK "i-brokenlink.png" + +#define ICON_NAME_SYMBOLIC_LINK_OVERLAY "i-symlink.png" + +/* This used to be called ICON_CACHE_MAX_ENTRIES, but it's misleading + * to call it that, since we can have any number of entries in the + * cache if the caller keeps the pixbuf around (we only get rid of + * items from the cache after the caller unref's them). +*/ +#define ICON_CACHE_COUNT 20 -#define ICON_CACHE_MAX_ENTRIES 10 -#define ICON_CACHE_SWEEP_TIMEOUT 10 +/* This is the number of milliseconds we wait before sweeping out + * items from the cache. + */ +#define ICON_CACHE_SWEEP_TIMEOUT (10 * 1000) -/* This allows us to do smarter caching */ -static guint use_counter = 0; +/* For now, images are used themselves as thumbnails when they are + * below this threshold size. Later we might have to have a more + * complex rule about when to use an image for itself. + */ +#define SELF_THUMBNAIL_SIZE_THRESHOLD 16384 -typedef struct { - guint ref_count; - char *name; - GdkPixbuf *plain, *symlink; - guint last_use; -} IconSet; - -typedef enum { - ICON_SET_DIRECTORY, - ICON_SET_DIRECTORY_CLOSED, - ICON_SET_EXECUTABLE, - ICON_SET_REGULAR, - ICON_SET_CORE, - ICON_SET_SOCKET, - ICON_SET_FIFO, - ICON_SET_CHARACTER_DEVICE, - ICON_SET_BLOCK_DEVICE, - ICON_SET_BROKEN_SYMBOLIC_LINK, - ICON_SET_FALLBACK, - ICON_SET_SPECIAL_LAST -} SpecialIconSetType; +/* This circular doubly-linked list structure is used to keep a list + * of the most recently used items in the cache. + */ +typedef struct NautilusCircularList NautilusCircularList; +struct NautilusCircularList { + NautilusCircularList *next; + NautilusCircularList *prev; +}; +/* The icon factory. + * These are actually globals, but they're in a structure so we can + * have multiple icon factories some day if we want to. + */ typedef struct { char *theme_name; - GHashTable *name_to_image; - IconSet special_icon_sets[ICON_SET_SPECIAL_LAST]; + /* A hash table so we pass out the same scalable icon pointer + * every time someone asks for the same icon. Scalable icons + * are removed from this hash table when they are destroyed. + */ + GHashTable *scalable_icons; - GdkPixbuf *symlink_overlay; - + /* A hash table that contains a cache of actual images. + * A circular list of the most recently used images is kept + * around, and we don't let them go when we sweep the cache. + */ + GHashTable *icon_cache; + NautilusCircularList recently_used_dummy_head; + guint recently_used_count; guint sweep_timer; + + /* An overlay for symbolic link icons. This is probably going + * to go away when we switch to using little icon badges for + * various keywords. + */ + GdkPixbuf *symbolic_link_overlay; } NautilusIconFactory; +/* A scalable icon, which is basically the name and path of an icon, + * before we load the actual pixels of the icons's image. + */ struct _NautilusScalableIcon { guint ref_count; char *uri; - IconSet *icon_set; + char *name; gboolean is_symbolic_link; }; -/* forward declarations */ -static NautilusIconFactory * nautilus_get_current_icon_factory (void); -static GdkPixbuf * nautilus_icon_factory_scale (NautilusIconFactory *factory, - GdkPixbuf *standard_sized_pixbuf, - guint size_in_pixels); -static NautilusScalableIcon *scalable_icon_get (const char *uri, - IconSet *icon_set, - gboolean is_symbolic_link); - -static IconSet * -icon_set_new (const gchar *name) -{ - IconSet *new; +/* The key to a hash table that holds the scaled icons as pixbufs. + */ +typedef struct { + NautilusScalableIcon *scalable_icon; + guint size_in_pixels; - new = g_new0 (IconSet, 1); - new->name = g_strdup (name); + NautilusCircularList recently_used_node; +} NautilusIconCacheKey; - return new; -} +/* forward declarations */ -static void -icon_set_destroy (IconSet *icon_set, gboolean free_name) +static NautilusIconFactory * nautilus_get_current_icon_factory (void); +static NautilusIconFactory * nautilus_icon_factory_new (const char *theme_name); +static GdkPixbuf * nautilus_icon_factory_scale (GdkPixbuf *standard_sized_image, + guint size_in_pixels); +static NautilusScalableIcon *nautilus_scalable_icon_get (const char *uri, + const char *name, + gboolean is_symbolic_link); +static guint nautilus_scalable_icon_hash (gconstpointer p); +static gboolean nautilus_scalable_icon_equal (gconstpointer a, + gconstpointer b); +static void nautilus_icon_cache_key_destroy (NautilusIconCacheKey *key); +static guint nautilus_icon_cache_key_hash (gconstpointer p); +static gboolean nautilus_icon_cache_key_equal (gconstpointer a, + gconstpointer b); + +/* Return a pointer to the single global icon factory. */ +NautilusIconFactory * +nautilus_get_current_icon_factory (void) { - if (icon_set == NULL) - return; - - if (free_name) - g_free (icon_set->name); - if (icon_set->plain != NULL) - gdk_pixbuf_unref (icon_set->plain); - if (icon_set->symlink != NULL) - gdk_pixbuf_unref (icon_set->symlink); + static NautilusIconFactory *global_icon_factory = NULL; + if (global_icon_factory == NULL) + global_icon_factory = nautilus_icon_factory_new (NULL); + return global_icon_factory; } +/* Create the icon factory. */ static NautilusIconFactory * nautilus_icon_factory_new (const char *theme_name) { @@ -125,44 +162,47 @@ nautilus_icon_factory_new (const char *theme_name) factory = g_new0 (NautilusIconFactory, 1); factory->theme_name = g_strdup (theme_name); - factory->name_to_image = g_hash_table_new (g_str_hash, g_str_equal); - factory->special_icon_sets[ICON_SET_DIRECTORY].name = "i-directory.png"; - factory->special_icon_sets[ICON_SET_DIRECTORY_CLOSED].name = "i-dirclosed.png"; - factory->special_icon_sets[ICON_SET_EXECUTABLE].name = "i-executable.png"; - factory->special_icon_sets[ICON_SET_REGULAR].name = "i-regular.png"; - factory->special_icon_sets[ICON_SET_CORE].name = "i-core.png"; - factory->special_icon_sets[ICON_SET_SOCKET].name = "i-sock.png"; - factory->special_icon_sets[ICON_SET_FIFO].name = "i-fifo.png"; - factory->special_icon_sets[ICON_SET_CHARACTER_DEVICE].name = "i-chardev.png"; - factory->special_icon_sets[ICON_SET_BLOCK_DEVICE].name = "i-blockdev.png"; - factory->special_icon_sets[ICON_SET_BROKEN_SYMBOLIC_LINK].name = "i-brokenlink.png"; - factory->special_icon_sets[ICON_SET_FALLBACK].name = ""; - + factory->scalable_icons = g_hash_table_new (nautilus_scalable_icon_hash, + nautilus_scalable_icon_equal); + factory->icon_cache = g_hash_table_new (nautilus_icon_cache_key_hash, + nautilus_icon_cache_key_equal); + + /* Empty out the recently-used list. */ + factory->recently_used_dummy_head.next = &factory->recently_used_dummy_head; + factory->recently_used_dummy_head.prev = &factory->recently_used_dummy_head; + return factory; } +/* Destroy one image in the cache. */ static gboolean -nautilus_icon_factory_destroy_icon_sets (gpointer key, gpointer value, gpointer user_data) +nautilus_icon_factory_destroy_cached_image (gpointer key, gpointer value, gpointer user_data) { - icon_set_destroy (value, TRUE); + nautilus_icon_cache_key_destroy (key); + gdk_pixbuf_unref (value); return TRUE; } +/* Reset the cache to the default state. */ static void -nautilus_icon_factory_invalidate (NautilusIconFactory *factory) +nautilus_icon_factory_clear (void) { - int i; + NautilusIconFactory *factory; - g_hash_table_foreach_remove (factory->name_to_image, - nautilus_icon_factory_destroy_icon_sets, + factory = nautilus_get_current_icon_factory (); + + g_hash_table_foreach_remove (factory->icon_cache, + nautilus_icon_factory_destroy_cached_image, NULL); - for (i = 0; i < ICON_SET_SPECIAL_LAST; i++) - icon_set_destroy (&factory->special_icon_sets[i], FALSE); + /* Empty out the recently-used list. */ + factory->recently_used_dummy_head.next = &factory->recently_used_dummy_head; + factory->recently_used_dummy_head.prev = &factory->recently_used_dummy_head; + factory->recently_used_count = 0; - if (factory->symlink_overlay) { - gdk_pixbuf_unref (factory->symlink_overlay); - factory->symlink_overlay = NULL; + if (factory->symbolic_link_overlay != NULL) { + gdk_pixbuf_unref (factory->symbolic_link_overlay); + factory->symbolic_link_overlay = NULL; } } @@ -171,8 +211,8 @@ nautilus_icon_factory_invalidate (NautilusIconFactory *factory) static void nautilus_icon_factory_destroy (NautilusIconFactory *factory) { - nautilus_icon_factory_invalidate (factory); - g_hash_table_destroy (factory->name_to_image); + nautilus_icon_factory_clear (); + g_hash_table_destroy (factory->icon_cache); g_free (factory->theme_name); g_free (factory); @@ -181,302 +221,368 @@ nautilus_icon_factory_destroy (NautilusIconFactory *factory) #endif static gboolean -icon_set_possibly_free (gpointer key, gpointer value, gpointer user_data) +nautilus_icon_factory_possibly_free_cached_image (gpointer key, + gpointer value, + gpointer user_data) { - IconSet *is = value; + NautilusIconCacheKey *icon_key; + GdkPixbuf *image; - if (is->last_use > (use_counter - ICON_CACHE_MAX_ENTRIES)) + /* Don't free a cache entry that is in the recently used list. */ + icon_key = key; + if (icon_key->recently_used_node.next != NULL) return FALSE; - if (is->plain && is->plain->ref_count <= 1) { - gdk_pixbuf_unref (is->plain); - is->plain = NULL; - } - - if (is->symlink && is->symlink->ref_count <= 1) { - gdk_pixbuf_unref (is->symlink); - is->symlink = NULL; - } - - if (is->symlink == NULL && is->plain == NULL && is->ref_count == 0) { - g_free (is->name); - return TRUE; - } + /* Don't free a cache entry if the image is still in use. */ + image = value; + if (image->ref_count > 1) + return FALSE; - return FALSE; + /* Free the item. */ + return nautilus_icon_factory_destroy_cached_image (key, value, NULL); } +/* Sweep the cache, freeing any images that are not in use and are + * also not recently used. + */ static gboolean -nautilus_icon_factory_sweep(gpointer data) +nautilus_icon_factory_sweep (gpointer user_data) { NautilusIconFactory *factory; - factory = data; + factory = user_data; + + g_hash_table_foreach_remove (factory->icon_cache, + nautilus_icon_factory_possibly_free_cached_image, + NULL); - g_hash_table_foreach_remove (factory->name_to_image, icon_set_possibly_free, NULL); factory->sweep_timer = 0; return FALSE; } +/* Schedule a timer to do a sweep. */ static void -nautilus_icon_factory_setup_sweep(NautilusIconFactory *factory) +nautilus_icon_factory_schedule_sweep (void) { - if (factory->sweep_timer) - return; + NautilusIconFactory *factory; - if (g_hash_table_size (factory->name_to_image) < ICON_CACHE_MAX_ENTRIES) + factory = nautilus_get_current_icon_factory (); + + if (factory->sweep_timer != 0) return; - factory->sweep_timer = g_timeout_add (ICON_CACHE_SWEEP_TIMEOUT * 1000, - nautilus_icon_factory_sweep, factory); + factory->sweep_timer = g_timeout_add (ICON_CACHE_SWEEP_TIMEOUT, + nautilus_icon_factory_sweep, + factory); } +/* Change the theme. */ void -nautilus_icon_factory_set_theme(const char *theme_name) +nautilus_icon_factory_set_theme (const char *theme_name) { NautilusIconFactory *factory; factory = nautilus_get_current_icon_factory (); - nautilus_icon_factory_invalidate (factory); + + nautilus_icon_factory_clear (); + g_free (factory->theme_name); factory->theme_name = g_strdup (theme_name); } -static IconSet * -nautilus_icon_factory_get_icon_set_for_file (NautilusIconFactory *factory, NautilusFile *file) +/* Use the MIME type to get the icon name. */ +static const char * +nautilus_icon_factory_get_icon_name_for_regular_file (NautilusFile *file) { - IconSet *icon_set; const char *mime_type; const char *icon_name; mime_type = nautilus_file_get_mime_type (file); - icon_name = NULL; - if (mime_type) + if (mime_type != NULL) { icon_name = gnome_mime_get_value (mime_type, "icon-filename"); + if (icon_name != NULL) + return icon_name; + } - if (icon_name) { - icon_set = g_hash_table_lookup (factory->name_to_image, icon_name); - if (!icon_set) { - icon_set = icon_set_new (icon_name); - g_hash_table_insert (factory->name_to_image, icon_set->name, icon_set); - } - } else { - /* We can't get a name, so we have to do some faking to figure out what set to load */ - if (nautilus_file_is_executable (file)) - icon_set = &factory->special_icon_sets[ICON_SET_EXECUTABLE]; - else - icon_set = &factory->special_icon_sets[ICON_SET_REGULAR]; - } + /* GNOME didn't give us a file name, so we have to fall back on special icon sets. */ + if (nautilus_file_is_executable (file)) + return ICON_NAME_EXECUTABLE; + return ICON_NAME_REGULAR; +} - return icon_set; +/* Get the icon name for a file. */ +static const char * +nautilus_icon_factory_get_icon_name_for_file (NautilusFile *file) +{ + /* Get an icon name based on the file's type. */ + switch (nautilus_file_get_type (file)) { + case GNOME_VFS_FILE_TYPE_DIRECTORY: + return ICON_NAME_DIRECTORY; + case GNOME_VFS_FILE_TYPE_FIFO: + return ICON_NAME_FIFO; + case GNOME_VFS_FILE_TYPE_SOCKET: + return ICON_NAME_SOCKET; + case GNOME_VFS_FILE_TYPE_CHARDEVICE: + return ICON_NAME_CHARACTER_DEVICE; + case GNOME_VFS_FILE_TYPE_BLOCKDEVICE: + return ICON_NAME_BLOCK_DEVICE; + case GNOME_VFS_FILE_TYPE_BROKENSYMLINK: + return ICON_NAME_BROKEN_SYMBOLIC_LINK; + case GNOME_VFS_FILE_TYPE_REGULAR: + case GNOME_VFS_FILE_TYPE_UNKNOWN: + default: + return nautilus_icon_factory_get_icon_name_for_regular_file (file); + } } +/* Given the icon name, load the pixbuf. */ static GdkPixbuf * -nautilus_icon_factory_load_file(NautilusIconFactory *factory, const char *fn) +nautilus_icon_factory_load_file (const char *name) { - char *file_name = NULL; - char cbuf[128]; + NautilusIconFactory *factory; + char *file_name; + char *partial_path; GdkPixbuf *image; - - if(*fn != '/') { - if(factory->theme_name) { - g_snprintf(cbuf, sizeof(cbuf), "nautilus/%s/%s", factory->theme_name, fn); - - file_name = gnome_pixmap_file(cbuf); + + factory = nautilus_get_current_icon_factory (); + + if (name[0] == '/') + file_name = g_strdup (name); + else { + /* Get theme version of icon. */ + file_name = NULL; + if (factory->theme_name != NULL) { + partial_path = g_strdup_printf ("nautilus/%s/%s", + factory->theme_name, name); + file_name = gnome_pixmap_file (partial_path); + g_free (partial_path); } - if(!file_name) { - g_snprintf(cbuf, sizeof(cbuf), "nautilus/%s", fn); - file_name = gnome_pixmap_file(cbuf); + /* Get non-theme version of icon. */ + if (file_name == NULL) { + partial_path = g_strdup_printf ("nautilus/%s", name); + file_name = gnome_pixmap_file (partial_path); + g_free (partial_path); } + + /* Can't find icon. Don't try to read it with a partial path. */ + if (file_name == NULL) + return NULL; } - image = gdk_pixbuf_new_from_file(file_name?file_name:fn); - g_free(file_name); - + /* Load the image. */ + image = gdk_pixbuf_new_from_file (file_name); + g_free (file_name); return image; } -/* Splats one on top of the other, putting the src pixbuf in the lower left corner of the dest pixbuf */ +/* Splats one on top of the other, putting the src image + * in the lower left corner of the dest image. + */ static void -my_gdk_pixbuf_composite(GdkPixbuf *dest, GdkPixbuf *src) +nautilus_gdk_pixbuf_composite_corner (GdkPixbuf *dest, GdkPixbuf *src) { int dx, dy, dw, dh; - dw = MIN(dest->art_pixbuf->width, src->art_pixbuf->width); - dh = MIN(dest->art_pixbuf->width, src->art_pixbuf->width); + dw = MIN (dest->art_pixbuf->width, src->art_pixbuf->width); + dh = MIN (dest->art_pixbuf->width, src->art_pixbuf->width); dx = dw - src->art_pixbuf->width; dy = dh - src->art_pixbuf->height; - gdk_pixbuf_composite(src, dest, dx, dy, dw, dh, 0, 0, 1, 1, ART_FILTER_BILINEAR, 255); + gdk_pixbuf_composite (src, dest, dx, dy, dw, dh, 0, 0, 1, 1, ART_FILTER_BILINEAR, 255); } +/* Given the icon name, load the pixbuf, falling back to the fallback + * icon if necessary. Also composite the symbolic link symbol as needed. + */ static GdkPixbuf * -nautilus_icon_factory_load_icon(NautilusIconFactory *factory, IconSet *is, gboolean is_symlink) +nautilus_icon_factory_load_icon (const char *name, gboolean is_symbolic_link) { GdkPixbuf *image; + NautilusIconFactory *factory; - if(is_symlink) - image = is->symlink; - else - image = is->plain; - - if (!image) { - if (*is->name == '\0') { - /* This is the fallback icon set */ - image = gdk_pixbuf_new_from_data ((guchar*)nautilus_default_file_icon, - ART_PIX_RGB, - nautilus_default_file_icon_has_alpha, - nautilus_default_file_icon_width, - nautilus_default_file_icon_height, - /* rowstride */ - nautilus_default_file_icon_width*4, - NULL, /* don't destroy data */ - NULL ); - } else { - /* need to load the file */ - image = nautilus_icon_factory_load_file(factory, is->name); - } - if (is_symlink) { - if (!factory->symlink_overlay) - factory->symlink_overlay = nautilus_icon_factory_load_file(factory, "i-symlink.png"); - - if(factory->symlink_overlay) - my_gdk_pixbuf_composite(image, factory->symlink_overlay); - is->symlink = image; - } else - is->plain = image; - } - - if (image) - gdk_pixbuf_ref(image); /* Returned value is owned by caller */ + /* Load the image. */ + image = nautilus_icon_factory_load_file (name); + if (image == NULL) + /* This is the fallback icon. */ + image = gdk_pixbuf_new_from_data (nautilus_default_file_icon, + ART_PIX_RGB, + nautilus_default_file_icon_has_alpha, + nautilus_default_file_icon_width, + nautilus_default_file_icon_height, + nautilus_default_file_icon_width * 4, /* stride */ + NULL, /* don't destroy data */ + NULL); + + /* Overlay the symbolic link symbol on top of the image. */ + if (is_symbolic_link) { + factory = nautilus_get_current_icon_factory (); + if (factory->symbolic_link_overlay == NULL) + factory->symbolic_link_overlay = nautilus_icon_factory_load_file + (ICON_NAME_SYMBOLIC_LINK_OVERLAY); + if (factory->symbolic_link_overlay != NULL) + nautilus_gdk_pixbuf_composite_corner + (image, factory->symbolic_link_overlay); + } return image; } +/* Get or create a scalable icon. */ static NautilusScalableIcon * -scalable_icon_new (const char *uri, - IconSet *icon_set, - gboolean is_symbolic_link) +nautilus_scalable_icon_get (const char *uri, + const char *name, + gboolean is_symbolic_link) { - NautilusScalableIcon *icon; - - g_return_val_if_fail (icon_set != NULL, NULL); - - icon = g_new (NautilusScalableIcon, 1); - icon->ref_count = 1; - icon->uri = g_strdup (uri); - icon->icon_set = icon_set; - icon->is_symbolic_link = is_symbolic_link; - - icon_set->ref_count++; + GHashTable *hash_table; + NautilusScalableIcon icon_key, *icon; + + /* Get at the hash table. */ + hash_table = nautilus_get_current_icon_factory ()->scalable_icons; + + /* Check to see if it's already in the table. */ + icon_key.uri = (char *)uri; + icon_key.name = (char *)name; + icon_key.is_symbolic_link = is_symbolic_link; + icon = g_hash_table_lookup (hash_table, &icon_key); + if (icon == NULL) { + /* Not in the table, so create it and put it in. */ + icon = g_new0 (NautilusScalableIcon, 1); + icon->uri = g_strdup (uri); + icon->name = g_strdup (name); + icon->is_symbolic_link = is_symbolic_link; + g_hash_table_insert (hash_table, icon, icon); + } + /* Grab a reference and return it. */ + nautilus_scalable_icon_ref (icon); return icon; } -static NautilusScalableIcon * -scalable_icon_get (const char *uri, - IconSet *icon_set, - gboolean is_symbolic_link) +void +nautilus_scalable_icon_ref (NautilusScalableIcon *icon) { - /* FIXME: These should come from a hash table. */ - return scalable_icon_new (uri, icon_set, is_symbolic_link); + g_return_if_fail (icon != NULL); + + icon->ref_count++; } void nautilus_scalable_icon_unref (NautilusScalableIcon *icon) { - g_return_if_fail (icon->ref_count != 0); + GHashTable *hash_table; + g_return_if_fail (icon != NULL); + g_return_if_fail (icon->ref_count != 0); + if (--icon->ref_count != 0) return; + hash_table = nautilus_get_current_icon_factory ()->scalable_icons; + g_hash_table_remove (hash_table, icon); + g_free (icon->uri); + g_free (icon->name); + g_free (icon); +} - g_assert (icon->icon_set->ref_count != 0); - icon->icon_set->ref_count--; +static guint +nautilus_scalable_icon_hash (gconstpointer p) +{ + const NautilusScalableIcon *icon; + guint hash; - g_free (icon); + icon = p; + hash = 0; + + if (icon->uri != NULL) + hash = g_str_hash (icon->uri); + + hash <<= 4; + if (icon->name != NULL) + hash ^= g_str_hash (icon->name); + + hash <<= 1; + hash |= icon->is_symbolic_link; + + return hash; +} + +static gboolean +nautilus_scalable_icon_equal (gconstpointer a, + gconstpointer b) +{ + const NautilusScalableIcon *icon_a, *icon_b; + + icon_a = a; + icon_b = b; + + return nautilus_strcmp (icon_a->uri, icon_b->uri) == 0 + && nautilus_strcmp (icon_a->name, icon_b->name) == 0 + && icon_a->is_symbolic_link == icon_b->is_symbolic_link; } NautilusScalableIcon * nautilus_icon_factory_get_icon_for_file (NautilusFile *file) { - NautilusIconFactory *factory; - IconSet *set; - char *uri, *custom_image; + char *uri; + const char *name; gboolean is_symbolic_link; NautilusScalableIcon *scalable_icon; - + if (file == NULL) return NULL; - factory = nautilus_get_current_icon_factory (); + /* If there is a custom image in the metadata, use that. + * Otherwise, consider using the image itself as a custom icon. + * The check for using the image is currently based on the file + * size, but that will change to a more sophisticated scheme later. + */ + uri = nautilus_file_get_metadata(file, NAUTILUS_CUSTOM_ICON_METADATA_KEY, NULL); + if (uri == NULL + && nautilus_has_prefix (nautilus_file_get_mime_type (file), "image/") + && nautilus_file_get_size (file) < SELF_THUMBNAIL_SIZE_THRESHOLD) + uri = nautilus_file_get_uri (file); - /* Get an icon set based on the file's type. */ - switch (nautilus_file_get_type (file)) { - case GNOME_VFS_FILE_TYPE_UNKNOWN: - case GNOME_VFS_FILE_TYPE_REGULAR: - default: - set = nautilus_icon_factory_get_icon_set_for_file (factory, file); - break; - case GNOME_VFS_FILE_TYPE_DIRECTORY: - set = &factory->special_icon_sets[ICON_SET_DIRECTORY]; - break; - case GNOME_VFS_FILE_TYPE_FIFO: - set = &factory->special_icon_sets[ICON_SET_FIFO]; - break; - case GNOME_VFS_FILE_TYPE_SOCKET: - set = &factory->special_icon_sets[ICON_SET_SOCKET]; - break; - case GNOME_VFS_FILE_TYPE_CHARDEVICE: - set = &factory->special_icon_sets[ICON_SET_CHARACTER_DEVICE]; - break; - case GNOME_VFS_FILE_TYPE_BLOCKDEVICE: - set = &factory->special_icon_sets[ICON_SET_BLOCK_DEVICE]; - break; - case GNOME_VFS_FILE_TYPE_BROKENSYMLINK: - set = &factory->special_icon_sets[ICON_SET_BROKEN_SYMBOLIC_LINK]; - break; - } + /* Get the generic icon set for this file. */ + name = nautilus_icon_factory_get_icon_name_for_file (file); /* Also record whether it's a symbolic link or not. * Later, we'll probably use a separate icon badge for this, - * but for now, we'll keep it. + * outside the icon factory machinery. But for now, we'll keep it. */ is_symbolic_link = nautilus_file_is_symbolic_link (file); - /* if there is a custom image in the metadata, use that instead */ - - custom_image = nautilus_file_get_metadata(file, "image", NULL); - if (custom_image != NULL) - uri = custom_image; - /* Use the image itself as a custom icon. */ - else if (nautilus_has_prefix (nautilus_file_get_mime_type (file), "image/") - && nautilus_file_get_size (file) < 16384) - uri = nautilus_file_get_uri (file); - else - uri = NULL; - /* Create the icon or find it in the cache if it's already there. */ - scalable_icon = scalable_icon_get (uri, set, is_symbolic_link); - if (uri != NULL) - g_free (uri); - - nautilus_icon_factory_setup_sweep (factory); + scalable_icon = nautilus_scalable_icon_get (uri, name, is_symbolic_link); + g_free (uri); return scalable_icon; } -GdkPixbuf * -nautilus_icon_factory_get_pixbuf_for_icon (NautilusScalableIcon *scalable_icon, - guint size_in_pixels) +static GdkPixbuf * +nautilus_icon_factory_create_image_for_icon (NautilusScalableIcon *scalable_icon, + guint size_in_pixels) { NautilusIconFactory *factory; - IconSet *set; - GdkPixbuf *image; + GdkPixbuf *image, *standard_size_image; + /* First cut at handling multiple sizes. If size is other than standard, + * scale the pixbuf here. Eventually we'll read in icon files at multiple + * sizes rather than relying on scaling in every case (though we'll still + * need scaling as a fallback). + */ + if (size_in_pixels != NAUTILUS_ICON_SIZE_STANDARD) + { + standard_size_image = nautilus_icon_factory_get_pixbuf_for_icon + (scalable_icon, NAUTILUS_ICON_SIZE_STANDARD); + image = nautilus_icon_factory_scale + (standard_size_image, size_in_pixels); + gdk_pixbuf_unref (standard_size_image); + return image; + } + factory = nautilus_get_current_icon_factory (); /* FIXME: This works only with file:// images, because there's @@ -488,82 +594,152 @@ nautilus_icon_factory_get_pixbuf_for_icon (NautilusScalableIcon *scalable_icon, image = gdk_pixbuf_new_from_file (scalable_icon->uri + 7); /* If there was no suitable custom icon URI, then use the icon set. */ - if (image == NULL) { - set = scalable_icon->icon_set; - set->last_use = use_counter++; + if (image == NULL) image = nautilus_icon_factory_load_icon - (factory, set, scalable_icon->is_symbolic_link); - } + (scalable_icon->name, scalable_icon->is_symbolic_link); - /* If the icon set failed, then use the fallback set. */ - if (image == NULL) { - g_warning ("failed to load icon, using fallback icon set"); - set = &factory->special_icon_sets[ICON_SET_FALLBACK]; - set->last_use = use_counter++; - image = nautilus_icon_factory_load_icon - (factory, set, scalable_icon->is_symbolic_link); - } - - g_assert (image != NULL); + return image; +} - /* First cut at handling multiple sizes. If size is other than standard, - * scale the pixbuf here. Eventually we'll store icons at multiple sizes - * rather than relying on scaling in every case (though we'll still need - * scaling as a fallback). We'll also cache the scaled pixbufs. - * For now, assume that the icon found so far is of standard size. - */ - if (size_in_pixels != NAUTILUS_ICON_SIZE_STANDARD) - { - GdkPixbuf *scaled_icon; +/* Move this item to the head of the recently-used list, + * bumping the last item off that list if necessary. + */ +static void +nautilus_icon_factory_mark_recently_used (NautilusCircularList *node) +{ + NautilusIconFactory *factory; + NautilusCircularList *head, *last_node; - scaled_icon = nautilus_icon_factory_scale - (factory, image, size_in_pixels); - gdk_pixbuf_unref (image); - image = scaled_icon; - } - - nautilus_icon_factory_setup_sweep (factory); - + factory = nautilus_get_current_icon_factory (); + head = &factory->recently_used_dummy_head; + + /* Move the node to the start of the list. */ + if (node->prev != head) { + if (node->next != NULL) { + /* Remove the node from its current position in the list. */ + node->next->prev = node->prev; + node->prev->next = node->next; + } else { + /* Node was not already in the list, so add it. + * If the list is already full, remove the last node. + */ + if (factory->recently_used_count < ICON_CACHE_COUNT) + factory->recently_used_count++; + else { + /* Remove the last node. */ + last_node = head->prev; + + g_assert (last_node != head); + g_assert (last_node != node); + + head->prev = last_node->prev; + last_node->prev->next = head; + + last_node->prev = NULL; + last_node->next = NULL; + } + } + + /* Insert the node at the head of the list. */ + node->prev = head; + node->next = head->next; + node->next->prev = node; + head->next = node; + } +} + +GdkPixbuf * +nautilus_icon_factory_get_pixbuf_for_icon (NautilusScalableIcon *scalable_icon, + guint size_in_pixels) +{ + NautilusIconFactory *factory; + GHashTable *hash_table; + NautilusIconCacheKey lookup_key, *key; + GdkPixbuf *image; + gpointer key_in_table, value; + + factory = nautilus_get_current_icon_factory (); + hash_table = factory->icon_cache; + + /* Check to see if it's already in the table. */ + lookup_key.scalable_icon = scalable_icon; + lookup_key.size_in_pixels = size_in_pixels; + if (g_hash_table_lookup_extended (hash_table, &lookup_key, &key_in_table, &value)) { + /* Found it in the table. */ + key = key_in_table; + image = value; + } else { + /* Not in the table, so create the image and put it in. */ + image = nautilus_icon_factory_create_image_for_icon + (scalable_icon, size_in_pixels); + + /* Create the key for the table. */ + key = g_new0 (NautilusIconCacheKey, 1); + nautilus_scalable_icon_ref (scalable_icon); + key->scalable_icon = scalable_icon; + key->size_in_pixels = size_in_pixels; + + /* Add the item to the hash table. */ + g_hash_table_insert (hash_table, key, image); + } + + /* Since this item was used, keep it in the cache longer. */ + nautilus_icon_factory_mark_recently_used (&key->recently_used_node); + + /* Come back later and sweep the cache. */ + nautilus_icon_factory_schedule_sweep (); + + /* Grab a ref for the caller. */ + gdk_pixbuf_ref (image); return image; } -NautilusIconFactory * -nautilus_get_current_icon_factory (void) +static void +nautilus_icon_cache_key_destroy (NautilusIconCacheKey *key) { - static NautilusIconFactory *global_icon_factory = NULL; - if (global_icon_factory == NULL) - global_icon_factory = nautilus_icon_factory_new (NULL); - return global_icon_factory; + nautilus_scalable_icon_unref (key->scalable_icon); +} + +static guint +nautilus_icon_cache_key_hash (gconstpointer p) +{ + const NautilusIconCacheKey *key; + + key = p; + return (guint)key->scalable_icon ^ key->size_in_pixels; +} + +static gboolean +nautilus_icon_cache_key_equal (gconstpointer a, gconstpointer b) +{ + const NautilusIconCacheKey *key_a, *key_b; + + key_a = a; + key_b = b; + + return key_a->scalable_icon == key_b->scalable_icon + && key_a->size_in_pixels == key_b->size_in_pixels; } static GdkPixbuf * -nautilus_icon_factory_scale (NautilusIconFactory *factory, - GdkPixbuf *standard_sized_pixbuf, - guint size_in_pixels) +nautilus_icon_factory_scale (GdkPixbuf *standard_sized_image, + guint size_in_pixels) { - GdkPixbuf *result; int old_width, old_height, new_width, new_height; - g_return_val_if_fail (standard_sized_pixbuf != NULL, NULL); - - old_width = gdk_pixbuf_get_width (standard_sized_pixbuf); - old_height = gdk_pixbuf_get_height (standard_sized_pixbuf); + old_width = gdk_pixbuf_get_width (standard_sized_image); + old_height = gdk_pixbuf_get_height (standard_sized_image); new_width = (old_width * size_in_pixels) / NAUTILUS_ICON_SIZE_STANDARD; new_height = (old_height * size_in_pixels) / NAUTILUS_ICON_SIZE_STANDARD; - /* This creates scaled icon with ref. count of 1. */ - result = gdk_pixbuf_scale_simple (standard_sized_pixbuf, - new_width, - new_height, - ART_FILTER_BILINEAR); - - return result; + return gdk_pixbuf_scale_simple (standard_sized_image, + new_width, + new_height, + ART_FILTER_BILINEAR); } - -/* - * Return nominal icon size for given zoom level. +/* Return nominal icon size for given zoom level. * @zoom_level: zoom level for which to find matching icon size. * * Return value: icon size between NAUTILUS_ICON_SIZE_SMALLEST and @@ -572,24 +748,23 @@ nautilus_icon_factory_scale (NautilusIconFactory *factory, guint nautilus_icon_size_for_zoom_level (NautilusZoomLevel zoom_level) { - switch (zoom_level) - { - case NAUTILUS_ZOOM_LEVEL_SMALLEST: - return NAUTILUS_ICON_SIZE_SMALLEST; - case NAUTILUS_ZOOM_LEVEL_SMALLER: - return NAUTILUS_ICON_SIZE_SMALLER; - case NAUTILUS_ZOOM_LEVEL_SMALL: - return NAUTILUS_ICON_SIZE_SMALL; - case NAUTILUS_ZOOM_LEVEL_STANDARD: - return NAUTILUS_ICON_SIZE_STANDARD; - case NAUTILUS_ZOOM_LEVEL_LARGE: - return NAUTILUS_ICON_SIZE_LARGE; - case NAUTILUS_ZOOM_LEVEL_LARGER: - return NAUTILUS_ICON_SIZE_LARGER; - case NAUTILUS_ZOOM_LEVEL_LARGEST: - return NAUTILUS_ICON_SIZE_LARGEST; - default: - g_assert_not_reached(); - return NAUTILUS_ICON_SIZE_STANDARD; + switch (zoom_level) { + case NAUTILUS_ZOOM_LEVEL_SMALLEST: + return NAUTILUS_ICON_SIZE_SMALLEST; + case NAUTILUS_ZOOM_LEVEL_SMALLER: + return NAUTILUS_ICON_SIZE_SMALLER; + case NAUTILUS_ZOOM_LEVEL_SMALL: + return NAUTILUS_ICON_SIZE_SMALL; + case NAUTILUS_ZOOM_LEVEL_STANDARD: + return NAUTILUS_ICON_SIZE_STANDARD; + case NAUTILUS_ZOOM_LEVEL_LARGE: + return NAUTILUS_ICON_SIZE_LARGE; + case NAUTILUS_ZOOM_LEVEL_LARGER: + return NAUTILUS_ICON_SIZE_LARGER; + case NAUTILUS_ZOOM_LEVEL_LARGEST: + return NAUTILUS_ICON_SIZE_LARGEST; + default: + g_assert_not_reached (); + return NAUTILUS_ICON_SIZE_STANDARD; } } |