summaryrefslogtreecommitdiff
path: root/libnautilus/nautilus-icon-factory.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnautilus/nautilus-icon-factory.c')
-rw-r--r--libnautilus/nautilus-icon-factory.c855
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;
}
}