/* nautilus-icon-info.c * Copyright (C) 2007 Red Hat, Inc., Alexander Larsson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, see . */ #include "nautilus-icon-info.h" #include "nautilus-enums.h" #include #include #include static GtkIconPaintable * lookup_themed_icon (GIcon *icon, int size, float scale); GIcon * nautilus_icon_info_get_default_file_icon (void) { static GIcon *fallback_icon = NULL; if (G_UNLIKELY (fallback_icon == NULL)) { char *icon_names[3] = {"application-x-generic", "text-x-generic", NULL}; fallback_icon = g_themed_icon_new_from_names (icon_names, -1); } return fallback_icon; } static GdkPaintable * nautilus_icon_info_get_fallback (int size, int scale) { static gboolean in_fallback = FALSE, no_fallback = FALSE; if (in_fallback || no_fallback) { /* Already tried to find a fallback, use hard coded fallback paintable */ static GtkSvg *fallback_paintable = NULL; g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); int dim = size * scale; if (fallback_paintable == NULL) { const char *resource_path = "/org/gnome/nautilus/image/text-x-preview.svg"; fallback_paintable = gtk_svg_new_from_resource (resource_path); } gdk_paintable_snapshot (GDK_PAINTABLE (fallback_paintable), snapshot, dim, dim); no_fallback = TRUE; return gtk_snapshot_to_paintable (snapshot, &GRAPHENE_SIZE_INIT (dim, dim)); } GIcon *icon = nautilus_icon_info_get_default_file_icon (); GdkPaintable *fallback_paintable; /* Use existing cache to cache the fallback paintable */ in_fallback = TRUE; fallback_paintable = nautilus_icon_info_lookup (icon, size, scale); in_fallback = FALSE; return fallback_paintable; } typedef struct { GIcon *icon; int scale; int size; } LoadableIconKey; typedef struct { char *icon_name; int scale; int size; } ThemedIconKey; static NautilusHashQueue *loadable_icon_cache = NULL; static NautilusHashQueue *themed_icon_cache = NULL; #define LOADABLE_ICON_CACHE_COUNT_LIMIT 100 #define THEMED_ICON_CACHE_COUNT_LIMIT 200 void nautilus_icon_info_clear_caches (void) { g_clear_pointer (&loadable_icon_cache, nautilus_hash_queue_destroy); g_clear_pointer (&themed_icon_cache, nautilus_hash_queue_destroy); } static guint int_hash (int v) { return g_direct_hash (GINT_TO_POINTER (v)); } static guint loadable_icon_key_hash (LoadableIconKey *key) { return g_icon_hash (key->icon) ^ int_hash (key->scale) ^ int_hash (key->size); } static gboolean loadable_icon_key_equal (const LoadableIconKey *a, const LoadableIconKey *b) { return a->size == b->size && a->scale == b->scale && g_icon_equal (a->icon, b->icon); } static LoadableIconKey * loadable_icon_key_new (GIcon *icon, int scale, int size) { LoadableIconKey *key; key = g_slice_new (LoadableIconKey); key->icon = g_object_ref (icon); key->scale = scale; key->size = size; return key; } static void loadable_icon_key_free (LoadableIconKey *key) { g_object_unref (key->icon); g_slice_free (LoadableIconKey, key); } static GdkPaintable * loadable_icon_cache_get (LoadableIconKey *key) { if (loadable_icon_cache == NULL) { return NULL; } GdkPaintable *paintable = nautilus_hash_queue_find_item (loadable_icon_cache, key); if (paintable != NULL) { nautilus_hash_queue_move_existing_to_tail (loadable_icon_cache, key); } return paintable; } static void loadable_icon_cache_add (LoadableIconKey *key, GdkPaintable *paintable) { if (G_UNLIKELY (loadable_icon_cache == NULL)) { loadable_icon_cache = nautilus_hash_queue_new ((GHashFunc) loadable_icon_key_hash, (GEqualFunc) loadable_icon_key_equal, (GDestroyNotify) loadable_icon_key_free, (GDestroyNotify) g_object_unref); } if (nautilus_hash_queue_reenqueue (loadable_icon_cache, key, paintable) && nautilus_hash_queue_get_length (loadable_icon_cache) > LOADABLE_ICON_CACHE_COUNT_LIMIT) { nautilus_hash_queue_remove_head (loadable_icon_cache); } } static guint themed_icon_key_hash (ThemedIconKey *key) { return g_str_hash (key->icon_name) ^ int_hash (key->scale) ^ int_hash (key->size); } static gboolean themed_icon_key_equal (const ThemedIconKey *a, const ThemedIconKey *b) { return a->size == b->size && a->scale == b->scale && g_str_equal (a->icon_name, b->icon_name); } static ThemedIconKey * themed_icon_key_new (const char *icon_name, int scale, int size) { ThemedIconKey *key; key = g_slice_new (ThemedIconKey); key->icon_name = g_strdup (icon_name); key->scale = scale; key->size = size; return key; } static void themed_icon_key_free (ThemedIconKey *key) { g_free (key->icon_name); g_slice_free (ThemedIconKey, key); } static GdkPaintable * themed_icon_cache_get (ThemedIconKey *key) { if (themed_icon_cache == NULL) { return NULL; } GdkPaintable *paintable = GDK_PAINTABLE (nautilus_hash_queue_find_item (themed_icon_cache, key)); if (paintable != NULL) { nautilus_hash_queue_move_existing_to_tail (themed_icon_cache, key); } return paintable; } static void themed_icon_cache_add (ThemedIconKey *key, GdkPaintable *paintable) { if (G_UNLIKELY (themed_icon_cache == NULL)) { themed_icon_cache = nautilus_hash_queue_new ((GHashFunc) themed_icon_key_hash, (GEqualFunc) themed_icon_key_equal, (GDestroyNotify) themed_icon_key_free, (GDestroyNotify) g_object_unref); } if (nautilus_hash_queue_reenqueue (themed_icon_cache, key, paintable) && nautilus_hash_queue_get_length (themed_icon_cache) > THEMED_ICON_CACHE_COUNT_LIMIT) { nautilus_hash_queue_remove_head (themed_icon_cache); } } static GtkIconTheme * get_icon_theme (void) { GtkIconTheme *theme = gtk_icon_theme_get_for_display (gdk_display_get_default ()); if (g_test_initialized ()) { /* During tests, force Adwaita theme */ static GtkIconTheme *test_theme = NULL; if (test_theme == NULL) { test_theme = gtk_icon_theme_new (); gtk_icon_theme_set_theme_name (test_theme, "Adwaita"); } theme = test_theme; } return theme; } static GtkIconPaintable * lookup_themed_icon (GIcon *icon, int size, float scale) { GtkIconTheme *theme = get_icon_theme (); if (!gtk_icon_theme_has_gicon (theme, icon)) { return NULL; } const gchar *generic_app_icon_name = "application-x-generic"; g_autoptr (GtkIconPaintable) icon_paintable = gtk_icon_theme_lookup_by_gicon (theme, icon, size, scale, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_NONE); const gchar *icon_name = gtk_icon_paintable_get_icon_name (icon_paintable); if (G_IS_THEMED_ICON (icon) && g_strcmp0 (generic_app_icon_name, icon_name) == 0) { /* GTK prefers generic icons in the main theme over exact match in * other themes even when the match is meaningless like the empty file * icon 'application-x-generic'. */ const gchar **names = (const gchar **) g_themed_icon_get_names (G_THEMED_ICON (icon)); if (g_strcmp0 (generic_app_icon_name, names[0]) != 0 && gtk_icon_theme_has_icon (theme, names[0])) { return gtk_icon_theme_lookup_icon (theme, names[0], NULL, size, scale, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_NONE); } } return g_steal_pointer (&icon_paintable); } GdkPaintable * nautilus_icon_info_lookup (GIcon *icon, int size, int scale) { GdkPaintable *paintable; if (G_IS_EMBLEMED_ICON (icon)) { icon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon)); } else if (G_IS_EMBLEM (icon)) { icon = g_emblem_get_icon (G_EMBLEM (icon)); } if (G_IS_LOADABLE_ICON (icon)) { LoadableIconKey lookup_key; LoadableIconKey *key; lookup_key.icon = icon; lookup_key.scale = scale; lookup_key.size = size; paintable = loadable_icon_cache_get (&lookup_key); if (paintable != NULL) { return g_object_ref (paintable); } g_autoptr (GInputStream) stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), size * scale, NULL, NULL, NULL); if (stream) { g_autoptr (GlyLoader) loader = gly_loader_new_for_stream (stream); g_autoptr (GlyImage) image = gly_loader_load (loader, NULL); if (image != NULL) { g_autoptr (GlyFrameRequest) frame_request = gly_frame_request_new (); g_autoptr (GlyFrame) frame = NULL; gly_frame_request_set_scale (frame_request, size * scale, size * scale); frame = gly_image_get_specific_frame (image, frame_request, NULL); if (frame != NULL) { double iw = gly_frame_get_width (frame); double ih = gly_frame_get_height (frame); double scale_factor = MIN ((double) size / iw, (double) size / ih); double width = iw * scale_factor; double height = ih * scale_factor; g_autoptr (GdkTexture) texture = gly_gtk_frame_get_texture (frame); g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); gdk_paintable_snapshot (GDK_PAINTABLE (texture), GDK_SNAPSHOT (snapshot), width, height); paintable = gtk_snapshot_to_paintable (snapshot, NULL); } } } if (paintable == NULL) { return nautilus_icon_info_get_fallback (size, scale); } key = loadable_icon_key_new (icon, scale, size); loadable_icon_cache_add (key, g_object_ref (paintable)); return paintable; } else if (G_IS_THEMED_ICON (icon)) { g_autoptr (GtkIconPaintable) icon_paintable = lookup_themed_icon (icon, size, scale); if (icon_paintable == NULL) { return nautilus_icon_info_get_fallback (size, scale); } ThemedIconKey lookup_key; ThemedIconKey *key; const char *icon_name; icon_name = gtk_icon_paintable_get_icon_name (icon_paintable); lookup_key.icon_name = (char *) icon_name; lookup_key.scale = scale; lookup_key.size = size; paintable = themed_icon_cache_get (&lookup_key); if (paintable != NULL) { return g_object_ref (paintable); } paintable = GDK_PAINTABLE (g_steal_pointer (&icon_paintable)); key = themed_icon_key_new (icon_name, scale, size); themed_icon_cache_add (key, g_object_ref (paintable)); return paintable; } else { return nautilus_icon_info_get_fallback (size, scale); } }