X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkicontheme.c;h=c79826c2adb2079e66357b57e6a4027b6be1e79e;hb=HEAD;hp=7a0a55771ba59e7b96efa9d2ce40864c6ebbd25a;hpb=4765f37484b28b99b18b8e7c96bc71754cfe9ebd;p=~andy%2Fgtk diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 7a0a55771..c79826c2a 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -12,9 +12,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" @@ -26,6 +24,7 @@ #endif #include #include +#include #include #include @@ -49,6 +48,99 @@ #include "gtksettings.h" #include "gtkprivate.h" +#undef GDK_DEPRECATED +#undef GDK_DEPRECATED_FOR +#define GDK_DEPRECATED +#define GDK_DEPRECATED_FOR(f) + +#include "deprecated/gtkstyle.h" + + +/** + * SECTION:gtkicontheme + * @Short_description: Looking up icons by name + * @Title: GtkIconTheme + * + * #GtkIconTheme provides a facility for looking up icons by name + * and size. The main reason for using a name rather than simply + * providing a filename is to allow different icons to be used + * depending on what icon theme is selected + * by the user. The operation of icon themes on Linux and Unix + * follows the Icon + * Theme Specification. There is a default icon theme, + * named hicolor where applications should install + * their icons, but more additional application themes can be + * installed as operating system vendors and users choose. + * + * Named icons are similar to the + * facility, and the distinction between the two may be a bit confusing. + * A few things to keep in mind: + * + * + * Stock images usually are used in conjunction with + * , such as %GTK_STOCK_OK or + * %GTK_STOCK_OPEN. Named icons are easier to set up and therefore + * are more useful for new icons that an application wants to + * add, such as application icons or window icons. + * + * + * Stock images can only be loaded at the symbolic sizes defined + * by the #GtkIconSize enumeration, or by custom sizes defined + * by gtk_icon_size_register(), while named icons are more flexible + * and any pixel size can be specified. + * + * + * Because stock images are closely tied to stock items, and thus + * to actions in the user interface, stock images may come in + * multiple variants for different widget states or writing + * directions. + * + * + * A good rule of thumb is that if there is a stock image for what + * you want to use, use it, otherwise use a named icon. It turns + * out that internally stock images are generally defined in + * terms of one or more named icons. (An example of the + * more than one case is icons that depend on writing direction; + * %GTK_STOCK_GO_FORWARD uses the two themed icons + * "gtk-stock-go-forward-ltr" and "gtk-stock-go-forward-rtl".) + * + * In many cases, named themes are used indirectly, via #GtkImage + * or stock items, rather than directly, but looking up icons + * directly is also simple. The #GtkIconTheme object acts + * as a database of all the icons in the current theme. You + * can create new #GtkIconTheme objects, but it's much more + * efficient to use the standard icon theme for the #GdkScreen + * so that the icon information is shared with other people + * looking up icons. In the case where the default screen is + * being used, looking up an icon can be as simple as: + * + * + * GError *error = NULL; + * GtkIconTheme *icon_theme; + * GdkPixbuf *pixbuf; + * + * icon_theme = gtk_icon_theme_get_default (); + * pixbuf = gtk_icon_theme_load_icon (icon_theme, + * "my-icon-name", // icon name + * 48, // size + * 0, // flags + * &error); + * if (!pixbuf) + * { + * g_warning ("Couldn't load icon: %s", error->message); + * g_error_free (error); + * } + * else + * { + * // Use the pixbuf + * g_object_unref (pixbuf); + * } + * + * + */ + + #define DEFAULT_THEME_NAME "hicolor" typedef enum @@ -69,27 +161,36 @@ typedef enum HAS_ICON_FILE = 1 << 3 } IconSuffix; +#define INFO_CACHE_LRU_SIZE 32 +#if 0 +#define DEBUG_CACHE(args) g_print args +#else +#define DEBUG_CACHE(args) +#endif struct _GtkIconThemePrivate { + GHashTable *info_cache; + GList *info_cache_lru; + + gchar *current_theme; + gchar *fallback_theme; + gchar **search_path; + gint search_path_len; + guint custom_theme : 1; guint is_screen_singleton : 1; guint pixbuf_supports_svg : 1; guint themes_valid : 1; guint check_reload : 1; guint loading_themes : 1; - - char *current_theme; - char *fallback_theme; - char **search_path; - int search_path_len; /* A list of all the themes needed to look up icons. * In search order, without duplicates */ GList *themes; GHashTable *unthemed_icons; - + /* Note: The keys of this hashtable are owned by the * themedir and unthemed hashtables. */ @@ -98,19 +199,48 @@ struct _GtkIconThemePrivate /* GdkScreen for the icon theme (may be NULL) */ GdkScreen *screen; - + /* time when we last stat:ed for theme changes */ - long last_stat_time; + glong last_stat_time; GList *dir_mtimes; gulong reset_styles_idle; }; +typedef struct { + gchar **icon_names; + gint size; + GtkIconLookupFlags flags; +} IconInfoKey; + +typedef struct _SymbolicPixbufCache SymbolicPixbufCache; + +struct _SymbolicPixbufCache { + GdkPixbuf *pixbuf; + GdkPixbuf *proxy_pixbuf; + GdkRGBA fg; + GdkRGBA success_color; + GdkRGBA warning_color; + GdkRGBA error_color; + SymbolicPixbufCache *next; +}; + +struct _GtkIconInfoClass +{ + GObjectClass parent_class; +}; + struct _GtkIconInfo { + GObject parent_instance; + /* Information about the source */ + IconInfoKey key; + GtkIconTheme *in_cache; + gchar *filename; + GFile *icon_file; GLoadableIcon *loadable; GSList *emblem_infos; @@ -118,7 +248,7 @@ struct _GtkIconInfo GdkPixbuf *cache_pixbuf; GtkIconData *data; - + /* Information about the directory where * the source was found */ @@ -131,16 +261,19 @@ struct _GtkIconInfo gint desired_size; guint raw_coordinates : 1; guint forced_size : 1; + guint emblems_applied : 1; /* Cached information if we go ahead and try to load * the icon. */ GdkPixbuf *pixbuf; + GdkPixbuf *proxy_pixbuf; GError *load_error; gdouble scale; - gboolean emblems_applied; - guint ref_count; + SymbolicPixbufCache *symbolic_pixbuf_cache; + + GtkRequisition *symbolic_pixbuf_size; }; typedef struct @@ -217,6 +350,7 @@ static void do_theme_change (GtkIconTheme *icon_theme); static void blow_themes (GtkIconTheme *icon_themes); static gboolean rescan_themes (GtkIconTheme *icon_themes); +static GtkIconData *icon_data_dup (GtkIconData *icon_data); static void icon_data_free (GtkIconData *icon_data); static void load_icon_data (IconThemeDir *dir, const char *path, @@ -236,6 +370,8 @@ static BuiltinIcon *find_builtin_icon (const gchar *icon_name, gint size, gint *min_difference_p, gboolean *has_larger_p); +static void remove_from_lru_cache (GtkIconTheme *icon_theme, + GtkIconInfo *icon_info); static guint signal_changed = 0; @@ -245,6 +381,46 @@ static GHashTable *icon_theme_builtin_icons; GtkIconCache *_builtin_cache = NULL; static GList *builtin_dirs = NULL; +static guint +icon_info_key_hash (gconstpointer _key) +{ + const IconInfoKey *key = _key; + guint h = 0; + int i; + for (i = 0; key->icon_names[i] != NULL; i++) + h ^= g_str_hash (key->icon_names[i]); + + h ^= key->size * 0x10001; + h ^= key->flags * 0x1000010; + + return h; +} + +static gboolean +icon_info_key_equal (gconstpointer _a, + gconstpointer _b) +{ + const IconInfoKey *a = _a; + const IconInfoKey *b = _b; + int i; + + if (a->size != b->size) + return FALSE; + + if (a->flags != b->flags) + return FALSE; + + for (i = 0; + a->icon_names[i] != NULL && + b->icon_names[i] != NULL; i++) + { + if (strcmp (a->icon_names[i], b->icon_names[i]) != 0) + return FALSE; + } + + return a->icon_names[i] == NULL && b->icon_names[i] == NULL; +} + G_DEFINE_TYPE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT) /** @@ -337,7 +513,7 @@ gtk_icon_theme_class_init (GtkIconThemeClass *klass) gobject_class->finalize = gtk_icon_theme_finalize; /** - * GtkIconTheme::changed + * GtkIconTheme::changed: * @icon_theme: the icon theme * * Emitted when the current icon theme is switched or GTK+ detects @@ -547,6 +723,25 @@ pixbuf_supports_svg (void) return found_svg; } +/* The icon info was removed from the icon_info_hash hash table */ +static void +icon_info_uncached (GtkIconInfo *icon_info) +{ + GtkIconTheme *icon_theme = icon_info->in_cache; + + DEBUG_CACHE (("removing %p (%s %d 0x%x) from cache (icon_them: %p) (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + icon_theme, + icon_theme != NULL ? g_hash_table_size (icon_theme->priv->info_cache) : 0)); + + icon_info->in_cache = NULL; + + if (icon_theme != NULL) + remove_from_lru_cache (icon_theme, icon_info); +} + static void gtk_icon_theme_init (GtkIconTheme *icon_theme) { @@ -559,6 +754,9 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme) GtkIconThemePrivate); icon_theme->priv = priv; + priv->info_cache = g_hash_table_new_full (icon_info_key_hash, icon_info_key_equal, NULL, + (GDestroyNotify)icon_info_uncached); + priv->custom_theme = FALSE; xdg_data_dirs = g_get_system_data_dirs (); @@ -569,8 +767,8 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme) priv->search_path = g_new (char *, priv->search_path_len); i = 0; - priv->search_path[i++] = g_build_filename (g_get_home_dir (), ".icons", NULL); priv->search_path[i++] = g_build_filename (g_get_user_data_dir (), "icons", NULL); + priv->search_path[i++] = g_build_filename (g_get_home_dir (), ".icons", NULL); for (j = 0; xdg_data_dirs[j]; j++) priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "icons", NULL); @@ -618,6 +816,8 @@ do_theme_change (GtkIconTheme *icon_theme) { GtkIconThemePrivate *priv = icon_theme->priv; + g_hash_table_remove_all (priv->info_cache); + if (!priv->themes_valid) return; @@ -640,10 +840,8 @@ blow_themes (GtkIconTheme *icon_theme) if (priv->themes_valid) { g_hash_table_destroy (priv->all_icons); - g_list_foreach (priv->themes, (GFunc)theme_destroy, NULL); - g_list_free (priv->themes); - g_list_foreach (priv->dir_mtimes, (GFunc)free_dir_mtime, NULL); - g_list_free (priv->dir_mtimes); + g_list_free_full (priv->themes, (GDestroyNotify) theme_destroy); + g_list_free_full (priv->dir_mtimes, (GDestroyNotify) free_dir_mtime); g_hash_table_destroy (priv->unthemed_icons); } priv->themes = NULL; @@ -663,6 +861,9 @@ gtk_icon_theme_finalize (GObject *object) icon_theme = GTK_ICON_THEME (object); priv = icon_theme->priv; + g_hash_table_destroy (priv->info_cache); + g_assert (priv->info_cache_lru == NULL); + if (priv->reset_styles_idle) { g_source_remove (priv->reset_styles_idle); @@ -893,7 +1094,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name) GKeyFile *theme_file; GError *error = NULL; IconThemeDirMtime *dir_mtime; - struct stat stat_buf; + GStatBuf stat_buf; priv = icon_theme->priv; @@ -1037,7 +1238,7 @@ load_themes (GtkIconTheme *icon_theme) IconSuffix old_suffix, new_suffix; GTimeVal tv; IconThemeDirMtime *dir_mtime; - struct stat stat_buf; + GStatBuf stat_buf; priv = icon_theme->priv; @@ -1129,9 +1330,10 @@ load_themes (GtkIconTheme *icon_theme) else unthemed_icon->no_svg_filename = abs_file; - g_hash_table_insert (priv->unthemed_icons, - base_name, - unthemed_icon); + /* takes ownership of base_name */ + g_hash_table_replace (priv->unthemed_icons, + base_name, + unthemed_icon); g_hash_table_insert (priv->all_icons, base_name, NULL); } @@ -1213,6 +1415,173 @@ ensure_valid_themes (GtkIconTheme *icon_theme) priv->loading_themes = FALSE; } +/* The LRU cache is a short list of IconInfos that are kept + alive even though their IconInfo would otherwise have + been freed, so that we can avoid reloading these + constantly. + We put infos on the lru list when nothing otherwise + references the info. So, when we get a cache hit + we remove it from the list, and when the proxy + pixmap is released we put it on the list. +*/ + +static void +ensure_lru_cache_space (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + GList *l; + + /* Remove last item if LRU full */ + l = g_list_nth (priv->info_cache_lru, INFO_CACHE_LRU_SIZE - 1); + if (l) + { + GtkIconInfo *icon_info = l->data; + + DEBUG_CACHE (("removing (due to out of space) %p (%s %d 0x%x) from LRU cache (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + g_list_length (priv->info_cache_lru))); + + priv->info_cache_lru = g_list_delete_link (priv->info_cache_lru, l); + g_object_unref (icon_info); + } +} + +static void +add_to_lru_cache (GtkIconTheme *icon_theme, + GtkIconInfo *icon_info) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + DEBUG_CACHE (("adding %p (%s %d 0x%x) to LRU cache (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + g_list_length (priv->info_cache_lru))); + + g_assert (g_list_find (priv->info_cache_lru, icon_info) == NULL); + + ensure_lru_cache_space (icon_theme); + /* prepend new info to LRU */ + priv->info_cache_lru = g_list_prepend (priv->info_cache_lru, + g_object_ref (icon_info)); +} + +static void +ensure_in_lru_cache (GtkIconTheme *icon_theme, + GtkIconInfo *icon_info) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + GList *l; + + l = g_list_find (priv->info_cache_lru, icon_info); + if (l) + { + /* Move to front of LRU if already in it */ + priv->info_cache_lru = g_list_remove_link (priv->info_cache_lru, l); + priv->info_cache_lru = g_list_concat (l, priv->info_cache_lru); + } + else + add_to_lru_cache (icon_theme, icon_info); +} + +static void +remove_from_lru_cache (GtkIconTheme *icon_theme, + GtkIconInfo *icon_info) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + if (g_list_find (priv->info_cache_lru, icon_info)) + { + DEBUG_CACHE (("removing %p (%s %d 0x%x) from LRU cache (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + g_list_length (priv->info_cache_lru))); + + priv->info_cache_lru = g_list_remove (priv->info_cache_lru, icon_info); + g_object_unref (icon_info); + } +} + +static SymbolicPixbufCache * +symbolic_pixbuf_cache_new (GdkPixbuf *pixbuf, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + SymbolicPixbufCache *next) +{ + SymbolicPixbufCache *cache; + + cache = g_new0 (SymbolicPixbufCache, 1); + cache->pixbuf = g_object_ref (pixbuf); + if (fg) + cache->fg = *fg; + if (success_color) + cache->success_color = *success_color; + if (warning_color) + cache->warning_color = *warning_color; + if (error_color) + cache->error_color = *error_color; + cache->next = next; + return cache; +} + +static gboolean +rgba_matches (const GdkRGBA *a, const GdkRGBA *b) +{ + GdkRGBA transparent = { 0 }; + + /* For matching we treat unset colors as transparent rather + than default, which works as well, because transparent + will never be used for real symbolic icon colors */ + if (a == NULL) + a = &transparent; + + return + fabs(a->red - b->red) < 0.0001 && + fabs(a->green - b->green) < 0.0001 && + fabs(a->blue - b->blue) < 0.0001 && + fabs(a->alpha - b->alpha) < 0.0001; +} + +static SymbolicPixbufCache * +symbolic_pixbuf_cache_matches (SymbolicPixbufCache *cache, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color) +{ + while (cache != NULL) + { + if (rgba_matches (fg, &cache->fg) && + rgba_matches (success_color, &cache->success_color) && + rgba_matches (warning_color, &cache->warning_color) && + rgba_matches (error_color, &cache->error_color)) + return cache; + + cache = cache->next; + } + + return NULL; +} + +static void +symbolic_pixbuf_cache_free (SymbolicPixbufCache *cache) +{ + SymbolicPixbufCache *next; + + while (cache != NULL) + { + next = cache->next; + g_object_unref (cache->pixbuf); + g_free (cache); + + cache = next; + } +} + static GtkIconInfo * choose_icon (GtkIconTheme *icon_theme, const gchar *icon_names[], @@ -1226,9 +1595,31 @@ choose_icon (GtkIconTheme *icon_theme, gboolean allow_svg; gboolean use_builtin; gint i; + IconInfoKey key; priv = icon_theme->priv; + ensure_valid_themes (icon_theme); + + key.icon_names = (char **)icon_names; + key.size = size; + key.flags = flags; + + icon_info = g_hash_table_lookup (priv->info_cache, &key); + if (icon_info != NULL) + { + DEBUG_CACHE (("cache hit %p (%s %d 0x%x) (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + g_hash_table_size (priv->info_cache))); + + icon_info = g_object_ref (icon_info); + remove_from_lru_cache (icon_theme, icon_info); + + return icon_info; + } + if (flags & GTK_ICON_LOOKUP_NO_SVG) allow_svg = FALSE; else if (flags & GTK_ICON_LOOKUP_FORCE_SVG) @@ -1237,8 +1628,23 @@ choose_icon (GtkIconTheme *icon_theme, allow_svg = priv->pixbuf_supports_svg; use_builtin = flags & GTK_ICON_LOOKUP_USE_BUILTIN; - - ensure_valid_themes (icon_theme); + + /* for symbolic icons, do a search in all registered themes first; + * a theme that inherits them from a parent theme might provide + * an alternative highcolor version, but still expect the symbolic icon + * to show up instead. + */ + if (icon_names[0] && + g_str_has_suffix (icon_names[0], "-symbolic")) + { + for (l = priv->themes; l; l = l->next) + { + IconTheme *theme = l->data; + icon_info = theme_lookup_icon (theme, icon_names[0], size, allow_svg, use_builtin); + if (icon_info) + goto out; + } + } for (l = priv->themes; l; l = l->next) { @@ -1300,15 +1706,28 @@ choose_icon (GtkIconTheme *icon_theme, else if (unthemed_icon->no_svg_filename) icon_info->filename = g_strdup (unthemed_icon->no_svg_filename); + icon_info->icon_file = g_file_new_for_path (icon_info->filename); + icon_info->dir_type = ICON_THEME_DIR_UNTHEMED; icon_info->dir_size = size; } out: - if (icon_info) + if (icon_info) { icon_info->desired_size = size; icon_info->forced_size = (flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0; + + icon_info->key.icon_names = g_strdupv ((char **)icon_names); + icon_info->key.size = size; + icon_info->key.flags = flags; + icon_info->in_cache = icon_theme; + DEBUG_CACHE (("adding %p (%s %d 0x%x) to cache (cache size %d)\n", + icon_info, + g_strjoinv (",", icon_info->key.icon_names), + icon_info->key.size, icon_info->key.flags, + g_hash_table_size (priv->info_cache))); + g_hash_table_insert (priv->info_cache, &icon_info->key, icon_info); } else { @@ -1333,10 +1752,10 @@ choose_icon (GtkIconTheme *icon_theme, if (!found) { - g_warning (_("Could not find the icon '%s'. The '%s' theme\n" - "was not found either, perhaps you need to install it.\n" - "You can get a copy from:\n" - "\t%s"), + g_warning ("Could not find the icon '%s'. The '%s' theme\n" + "was not found either, perhaps you need to install it.\n" + "You can get a copy from:\n" + "\t%s", icon_names[0], DEFAULT_THEME_NAME, "http://icon-theme.freedesktop.org/releases"); } } @@ -1359,9 +1778,8 @@ choose_icon (GtkIconTheme *icon_theme, * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() * combines these two steps if all you need is the pixbuf.) * - * Return value: a #GtkIconInfo structure containing information - * about the icon, or %NULL if the icon wasn't found. Free with - * gtk_icon_info_free() + * Return value: (transfer full): a #GtkIconInfo object containing information + * about the icon, or %NULL if the icon wasn't found. * * Since: 2.4 */ @@ -1437,9 +1855,8 @@ gtk_icon_theme_lookup_icon (GtkIconTheme *icon_theme, * tries them all in the given order before falling back to * inherited icon themes. * - * Return value: a #GtkIconInfo structure containing information - * about the icon, or %NULL if the icon wasn't found. Free with - * gtk_icon_info_free() + * Return value: (transfer full): a #GtkIconInfo object containing information + * about the icon, or %NULL if the icon wasn't found. * * Since: 2.12 */ @@ -1520,7 +1937,7 @@ gtk_icon_theme_load_icon (GtkIconTheme *icon_theme, } pixbuf = gtk_icon_info_load_icon (icon_info, error); - gtk_icon_info_free (icon_info); + g_object_unref (icon_info); return pixbuf; } @@ -1861,7 +2278,7 @@ rescan_themes (GtkIconTheme *icon_theme) IconThemeDirMtime *dir_mtime; GList *d; int stat_res; - struct stat stat_buf; + GStatBuf stat_buf; GTimeVal tv; priv = icon_theme->priv; @@ -1926,8 +2343,7 @@ theme_destroy (IconTheme *theme) g_free (theme->name); g_free (theme->example); - g_list_foreach (theme->dirs, (GFunc)theme_dir_destroy, NULL); - g_list_free (theme->dirs); + g_list_free_full (theme->dirs, (GDestroyNotify) theme_dir_destroy); g_free (theme); } @@ -2183,11 +2599,13 @@ theme_lookup_icon (IconTheme *theme, { file = g_strconcat (icon_name, string_from_suffix (suffix), NULL); icon_info->filename = g_build_filename (min_dir->dir, file, NULL); + icon_info->icon_file = g_file_new_for_path (icon_info->filename); g_free (file); } else { icon_info->filename = NULL; + icon_info->icon_file = NULL; } if (min_dir->icon_data != NULL) @@ -2326,6 +2744,7 @@ load_icon_data (IconThemeDir *dir, const char *path, const char *name) base_name = strip_suffix (name); data = g_slice_new0 (GtkIconData); + /* takes ownership of base_name */ g_hash_table_replace (dir->icon_data, base_name, data); ivalues = g_key_file_get_integer_list (icon_file, @@ -2424,6 +2843,7 @@ scan_directory (GtkIconThemePrivate *icon_theme, hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (dir->icons, base_name)); g_hash_table_replace (icon_theme->all_icons, base_name, NULL); + /* takes ownership of base_name */ g_hash_table_replace (dir->icons, base_name, GUINT_TO_POINTER (hash_suffix| suffix)); } @@ -2481,32 +2901,20 @@ theme_subdir_load (GtkIconTheme *icon_theme, g_free (context_string); } - max_size = g_key_file_get_integer (theme_file, subdir, "MaxSize", &error); - if (error) - { - max_size = size; - - g_error_free (error); - error = NULL; - } - - min_size = g_key_file_get_integer (theme_file, subdir, "MinSize", &error); - if (error) - { - min_size = size; + if (g_key_file_has_key (theme_file, subdir, "MaxSize", NULL)) + max_size = g_key_file_get_integer (theme_file, subdir, "MaxSize", NULL); + else + max_size = size; - g_error_free (error); - error = NULL; - } - - threshold = g_key_file_get_integer (theme_file, subdir, "Threshold", &error); - if (error) - { - threshold = 2; + if (g_key_file_has_key (theme_file, subdir, "MinSize", NULL)) + min_size = g_key_file_get_integer (theme_file, subdir, "MinSize", NULL); + else + min_size = size; - g_error_free (error); - error = NULL; - } + if (g_key_file_has_key (theme_file, subdir, "Threshold", NULL)) + threshold = g_key_file_get_integer (theme_file, subdir, "Threshold", NULL); + else + threshold = 2; for (d = icon_theme->priv->dir_mtimes; d; d = d->next) { @@ -2563,23 +2971,85 @@ icon_data_free (GtkIconData *icon_data) g_slice_free (GtkIconData, icon_data); } +static GtkIconData * +icon_data_dup (GtkIconData *icon_data) +{ + GtkIconData *dup = NULL; + if (icon_data) + { + dup = g_slice_new0 (GtkIconData); + *dup = *icon_data; + if (dup->n_attach_points > 0) + { + dup->attach_points = g_memdup (dup->attach_points, + sizeof (GdkPoint) * dup->n_attach_points); + } + dup->display_name = g_strdup (dup->display_name); + } + + return dup; +} + + /* * GtkIconInfo */ -G_DEFINE_BOXED_TYPE (GtkIconInfo, gtk_icon_info, - gtk_icon_info_copy, - gtk_icon_info_free) +static void gtk_icon_info_class_init (GtkIconInfoClass *klass); + +G_DEFINE_TYPE (GtkIconInfo, gtk_icon_info, G_TYPE_OBJECT) + +static void +gtk_icon_info_init (GtkIconInfo *icon_info) +{ + icon_info->scale = -1.; +} static GtkIconInfo * icon_info_new (void) { - GtkIconInfo *icon_info = g_slice_new0 (GtkIconInfo); + return g_object_new (GTK_TYPE_ICON_INFO, NULL); +} + +/* This only copies whatever is needed to load the pixbuf, so that we can do + * a load in a thread without affecting the original IconInfo from the thread. + */ +static GtkIconInfo * +icon_info_dup (GtkIconInfo *icon_info) +{ + GtkIconInfo *dup; + GSList *l; - icon_info->scale = -1.; - icon_info->ref_count = 1; + dup = icon_info_new (); - return icon_info; + dup->filename = g_strdup (icon_info->filename); + if (icon_info->icon_file) + dup->icon_file = g_object_ref (icon_info->icon_file); + if (icon_info->loadable) + dup->loadable = g_object_ref (icon_info->loadable); + if (icon_info->pixbuf) + dup->pixbuf = g_object_ref (icon_info->pixbuf); + + for (l = icon_info->emblem_infos; l != NULL; l = l->next) + { + dup->emblem_infos = + g_slist_append (dup->emblem_infos, + icon_info_dup (l->data)); + } + + if (icon_info->cache_pixbuf) + dup->cache_pixbuf = g_object_ref (icon_info->cache_pixbuf); + + dup->data = icon_data_dup (icon_info->data); + dup->dir_type = icon_info->dir_type; + dup->dir_size = icon_info->dir_size; + dup->threshold = icon_info->threshold; + dup->desired_size = icon_info->desired_size; + dup->raw_coordinates = icon_info->raw_coordinates; + dup->forced_size = icon_info->forced_size; + dup->emblems_applied = icon_info->emblems_applied; + + return dup; } static GtkIconInfo * @@ -2596,7 +3066,7 @@ icon_info_new_builtin (BuiltinIcon *icon) } /** - * gtk_icon_info_copy: + * gtk_icon_info_copy: (skip) * @icon_info: a #GtkIconInfo * * Make a copy of a #GtkIconInfo. @@ -2604,6 +3074,8 @@ icon_info_new_builtin (BuiltinIcon *icon) * Return value: the new GtkIconInfo * * Since: 2.4 + * + * Deprecated: 3.8: Use g_object_ref() **/ GtkIconInfo * gtk_icon_info_copy (GtkIconInfo *icon_info) @@ -2611,39 +3083,61 @@ gtk_icon_info_copy (GtkIconInfo *icon_info) g_return_val_if_fail (icon_info != NULL, NULL); - icon_info->ref_count++; - - return icon_info; + return g_object_ref (icon_info); } /** - * gtk_icon_info_free: + * gtk_icon_info_free: (skip) * @icon_info: a #GtkIconInfo * * Free a #GtkIconInfo and associated information * * Since: 2.4 + * + * Deprecated: 3.8: Use g_object_unref() **/ void gtk_icon_info_free (GtkIconInfo *icon_info) { g_return_if_fail (icon_info != NULL); - icon_info->ref_count--; - if (icon_info->ref_count > 0) - return; - + g_object_unref (icon_info); +} + +static void +gtk_icon_info_finalize (GObject *object) +{ + GtkIconInfo *icon_info = (GtkIconInfo *) object; + + if (icon_info->in_cache) + g_hash_table_remove (icon_info->in_cache->priv->info_cache, &icon_info->key); + + g_strfreev (icon_info->key.icon_names); + g_free (icon_info->filename); + g_clear_object (&icon_info->icon_file); + if (icon_info->loadable) g_object_unref (icon_info->loadable); - g_slist_foreach (icon_info->emblem_infos, (GFunc)gtk_icon_info_free, NULL); - g_slist_free (icon_info->emblem_infos); + g_slist_free_full (icon_info->emblem_infos, (GDestroyNotify) g_object_unref); if (icon_info->pixbuf) g_object_unref (icon_info->pixbuf); if (icon_info->cache_pixbuf) g_object_unref (icon_info->cache_pixbuf); + if (icon_info->symbolic_pixbuf_size) + gtk_requisition_free (icon_info->symbolic_pixbuf_size); + + symbolic_pixbuf_cache_free (icon_info->symbolic_pixbuf_cache); + + G_OBJECT_CLASS (gtk_icon_info_parent_class)->finalize (object); +} + +static void +gtk_icon_info_class_init (GtkIconInfoClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - g_slice_free (GtkIconInfo, icon_info); + gobject_class->finalize = gtk_icon_info_finalize; } /** @@ -2688,7 +3182,7 @@ gtk_icon_info_get_base_size (GtkIconInfo *icon_info) * * Since: 2.4 **/ -G_CONST_RETURN gchar * +const gchar * gtk_icon_info_get_filename (GtkIconInfo *icon_info) { g_return_val_if_fail (icon_info != NULL, NULL); @@ -2808,6 +3302,21 @@ apply_emblems (GtkIconInfo *info) info->emblems_applied = TRUE; } +/* If this returns TRUE, its safe to call + icon_info_ensure_scale_and_pixbuf without blocking */ +static gboolean +icon_info_get_pixbuf_ready (GtkIconInfo *icon_info) +{ + if (icon_info->pixbuf && + (icon_info->emblem_infos == NULL || icon_info->emblems_applied)) + return TRUE; + + if (icon_info->load_error) + return TRUE; + + return FALSE; +} + /* This function contains the complicated logic for deciding * on the size at which to load the icon and loading it at * that size. @@ -2838,14 +3347,8 @@ icon_info_ensure_scale_and_pixbuf (GtkIconInfo *icon_info, /* SVG icons are a special case - we just immediately scale them * to the desired size */ - if (icon_info->filename && !icon_info->loadable) - { - GFile *file; - - file = g_file_new_for_path (icon_info->filename); - icon_info->loadable = G_LOADABLE_ICON (g_file_icon_new (file)); - g_object_unref (file); - } + if (icon_info->icon_file && !icon_info->loadable) + icon_info->loadable = G_LOADABLE_ICON (g_file_icon_new (icon_info->icon_file)); is_svg = FALSE; if (G_IS_FILE_ICON (icon_info->loadable)) @@ -2995,6 +3498,22 @@ icon_info_ensure_scale_and_pixbuf (GtkIconInfo *icon_info, return TRUE; } +static void +proxy_pixbuf_destroy (guchar *pixels, gpointer data) +{ + GtkIconInfo *icon_info = data; + GtkIconTheme *icon_theme = icon_info->in_cache; + + g_assert (icon_info->proxy_pixbuf != NULL); + icon_info->proxy_pixbuf = NULL; + + /* Keep it alive a bit longer */ + if (icon_theme != NULL) + ensure_in_lru_cache (icon_theme, icon_info); + + g_object_unref (icon_info); +} + /** * gtk_icon_info_load_icon: * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() @@ -3040,90 +3559,325 @@ gtk_icon_info_load_icon (GtkIconInfo *icon_info, return NULL; } - return g_object_ref (icon_info->pixbuf); + /* Instead of returning the pixbuf directly we + return a proxy to it that we don't own (but that + shares the data with the one we own). This way + we can know when it is freed and ensure the + IconInfo is alive (and thus cached) while + the pixbuf is still alive. */ + + if (icon_info->proxy_pixbuf != NULL) + return g_object_ref (icon_info->proxy_pixbuf); + + icon_info->proxy_pixbuf = + gdk_pixbuf_new_from_data (gdk_pixbuf_get_pixels (icon_info->pixbuf), + gdk_pixbuf_get_colorspace (icon_info->pixbuf), + gdk_pixbuf_get_has_alpha (icon_info->pixbuf), + gdk_pixbuf_get_bits_per_sample (icon_info->pixbuf), + gdk_pixbuf_get_width (icon_info->pixbuf), + gdk_pixbuf_get_height (icon_info->pixbuf), + gdk_pixbuf_get_rowstride (icon_info->pixbuf), + proxy_pixbuf_destroy, + g_object_ref (icon_info)); + + return icon_info->proxy_pixbuf; } -static gchar * -gdk_color_to_css (GdkColor *color) +static void +load_icon_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { - return g_strdup_printf ("rgb(%d,%d,%d)", - color->red >> 8, - color->green >> 8, - color->blue >> 8); -} + GtkIconInfo *dup = task_data; -static gchar * -gdk_rgba_to_css (const GdkRGBA *color) -{ - /* drop alpha for now, since librsvg does not understand rgba() */ - return g_strdup_printf ("rgb(%d,%d,%d)", - (gint)(color->red * 255), - (gint)(color->green * 255), - (gint)(color->blue * 255)); + icon_info_ensure_scale_and_pixbuf (dup, FALSE); + g_task_return_pointer (task, NULL, NULL); +} + +/** + * gtk_icon_info_load_icon_async: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call when the + * request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Asynchronously load, render and scale an icon previously looked up + * from the icon theme using gtk_icon_theme_lookup_icon(). + * + * For more details, see gtk_icon_info_load_icon() which is the synchronous + * version of this call. + * + * Since: 3.8 + **/ +void +gtk_icon_info_load_icon_async (GtkIconInfo *icon_info, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GdkPixbuf *pixbuf; + GtkIconInfo *dup; + GError *error = NULL; + + task = g_task_new (icon_info, cancellable, callback, user_data); + + if (icon_info_get_pixbuf_ready (icon_info)) + { + pixbuf = gtk_icon_info_load_icon (icon_info, &error); + if (pixbuf == NULL) + g_task_return_error (task, error); + else + g_task_return_pointer (task, pixbuf, g_object_unref); + g_object_unref (task); + } + else + { + dup = icon_info_dup (icon_info); + g_task_set_task_data (task, dup, g_object_unref); + g_task_run_in_thread (task, load_icon_thread); + g_object_unref (task); + } +} + +/** + * gtk_icon_info_load_icon_finish: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @res: a #GAsyncResult + * @error: (allow-none): location to store error information on failure, + * or %NULL. + * + * Finishes an async icon load, see gtk_icon_info_load_icon_async(). + * + * Return value: (transfer full): the rendered icon; this may be a newly + * created icon or a new reference to an internal icon, so you must + * not modify the icon. Use g_object_unref() to release your reference + * to the icon. + * + * Since: 3.8 + **/ +GdkPixbuf * +gtk_icon_info_load_icon_finish (GtkIconInfo *icon_info, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + GtkIconInfo *dup; + + g_return_val_if_fail (g_task_is_valid (result, icon_info), NULL); + + dup = g_task_get_task_data (task); + if (dup == NULL || g_task_had_error (task)) + return g_task_propagate_pointer (task, error); + + /* We ran the thread and it was not cancelled */ + + /* Check if someone else updated the icon_info in between */ + if (!icon_info_get_pixbuf_ready (icon_info)) + { + /* If not, copy results from dup back to icon_info */ + + icon_info->emblems_applied = dup->emblems_applied; + icon_info->scale = dup->scale; + g_clear_object (&icon_info->pixbuf); + if (dup->pixbuf) + icon_info->pixbuf = g_object_ref (dup->pixbuf); + g_clear_error (&icon_info->load_error); + if (dup->load_error) + icon_info->load_error = g_error_copy (dup->load_error); + } + + g_assert (icon_info_get_pixbuf_ready (icon_info)); + + /* This is now guaranteed to not block */ + return gtk_icon_info_load_icon (icon_info, error); +} + +static gchar * +gdk_color_to_css (GdkColor *color) +{ + return g_strdup_printf ("rgb(%d,%d,%d)", + color->red >> 8, + color->green >> 8, + color->blue >> 8); +} + +static gchar * +gdk_rgba_to_css (const GdkRGBA *color) +{ + /* drop alpha for now, since librsvg does not understand rgba() */ + return g_strdup_printf ("rgb(%d,%d,%d)", + (gint)(color->red * 255), + (gint)(color->green * 255), + (gint)(color->blue * 255)); +} + +static void +proxy_symbolic_pixbuf_destroy (guchar *pixels, gpointer data) +{ + GtkIconInfo *icon_info = data; + GtkIconTheme *icon_theme = icon_info->in_cache; + SymbolicPixbufCache *symbolic_cache; + + for (symbolic_cache = icon_info->symbolic_pixbuf_cache; + symbolic_cache != NULL; + symbolic_cache = symbolic_cache->next) + { + if (symbolic_cache->proxy_pixbuf != NULL && + gdk_pixbuf_get_pixels (symbolic_cache->proxy_pixbuf) == pixels) + break; + } + + g_assert (symbolic_cache != NULL); + g_assert (symbolic_cache->proxy_pixbuf != NULL); + + symbolic_cache->proxy_pixbuf = NULL; + + /* Keep it alive a bit longer */ + if (icon_theme != NULL) + ensure_in_lru_cache (icon_theme, icon_info); + + g_object_unref (icon_info); +} + +static GdkPixbuf * +symbolic_cache_get_proxy (SymbolicPixbufCache *symbolic_cache, + GtkIconInfo *icon_info) +{ + if (symbolic_cache->proxy_pixbuf) + return g_object_ref (symbolic_cache->proxy_pixbuf); + + symbolic_cache->proxy_pixbuf = + gdk_pixbuf_new_from_data (gdk_pixbuf_get_pixels (symbolic_cache->pixbuf), + gdk_pixbuf_get_colorspace (symbolic_cache->pixbuf), + gdk_pixbuf_get_has_alpha (symbolic_cache->pixbuf), + gdk_pixbuf_get_bits_per_sample (symbolic_cache->pixbuf), + gdk_pixbuf_get_width (symbolic_cache->pixbuf), + gdk_pixbuf_get_height (symbolic_cache->pixbuf), + gdk_pixbuf_get_rowstride (symbolic_cache->pixbuf), + proxy_symbolic_pixbuf_destroy, + g_object_ref (icon_info)); + + return symbolic_cache->proxy_pixbuf; } static GdkPixbuf * _gtk_icon_info_load_symbolic_internal (GtkIconInfo *icon_info, - const gchar *css_fg, - const gchar *css_success, - const gchar *css_warning, - const gchar *css_error, - GError **error) + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + gboolean use_cache, + GError **error) { GInputStream *stream; GdkPixbuf *pixbuf; + gchar *css_fg; + gchar *css_success; + gchar *css_warning; + gchar *css_error; gchar *data; - gchar *success, *warning, *err; + gchar *width, *height, *uri; + SymbolicPixbufCache *symbolic_cache; + + if (use_cache) + { + symbolic_cache = symbolic_pixbuf_cache_matches (icon_info->symbolic_pixbuf_cache, + fg, success_color, warning_color, error_color); + if (symbolic_cache) + return symbolic_cache_get_proxy (symbolic_cache, icon_info); + } /* css_fg can't possibly have failed, otherwise * that would mean we have a broken style */ - g_return_val_if_fail (css_fg != NULL, NULL); + g_return_val_if_fail (fg != NULL, NULL); - success = warning = err = NULL; + css_fg = gdk_rgba_to_css (fg); + + css_success = css_warning = css_error = NULL; + + if (warning_color) + css_warning = gdk_rgba_to_css (warning_color); + + if (error_color) + css_error = gdk_rgba_to_css (error_color); + + if (success_color) + css_success = gdk_rgba_to_css (success_color); if (!css_success) { GdkColor success_default_color = { 0, 0x4e00, 0x9a00, 0x0600 }; - success = gdk_color_to_css (&success_default_color); + css_success = gdk_color_to_css (&success_default_color); } if (!css_warning) { GdkColor warning_default_color = { 0, 0xf500, 0x7900, 0x3e00 }; - warning = gdk_color_to_css (&warning_default_color); + css_warning = gdk_color_to_css (&warning_default_color); } if (!css_error) { GdkColor error_default_color = { 0, 0xcc00, 0x0000, 0x0000 }; - err = gdk_color_to_css (&error_default_color); + css_error = gdk_color_to_css (&error_default_color); } + if (!icon_info->symbolic_pixbuf_size) + { + stream = G_INPUT_STREAM (g_file_read (icon_info->icon_file, NULL, error)); + + if (!stream) + return NULL; + + /* Fetch size from the original icon */ + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); + g_object_unref (stream); + + if (!pixbuf) + return NULL; + + icon_info->symbolic_pixbuf_size = gtk_requisition_new (); + icon_info->symbolic_pixbuf_size->width = gdk_pixbuf_get_width (pixbuf); + icon_info->symbolic_pixbuf_size->height = gdk_pixbuf_get_height (pixbuf); + g_object_unref (pixbuf); + } + + width = g_strdup_printf ("%d", icon_info->symbolic_pixbuf_size->width); + height = g_strdup_printf ("%d", icon_info->symbolic_pixbuf_size->height); + uri = g_file_get_uri (icon_info->icon_file); data = g_strconcat ("\n" "\n" + " width=\"", width, "\"\n" + " height=\"", height, "\">\n" " \n" - " filename, "\"/>\n" + " \n" "", NULL); - g_free (warning); - g_free (err); - g_free (success); + g_free (css_fg); + g_free (css_warning); + g_free (css_error); + g_free (css_success); + g_free (width); + g_free (height); + g_free (uri); stream = g_memory_input_stream_new_from_data (data, -1, g_free); pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream, @@ -3134,7 +3888,21 @@ _gtk_icon_info_load_symbolic_internal (GtkIconInfo *icon_info, error); g_object_unref (stream); - return pixbuf; + if (pixbuf != NULL) + { + if (use_cache) + { + icon_info->symbolic_pixbuf_cache = + symbolic_pixbuf_cache_new (pixbuf, fg, success_color, warning_color, error_color, + icon_info->symbolic_pixbuf_cache); + g_object_unref (pixbuf); + return symbolic_cache_get_proxy (icon_info->symbolic_pixbuf_cache, icon_info); + } + else + return pixbuf; + } + + return NULL; } /** @@ -3183,48 +3951,30 @@ gtk_icon_info_load_symbolic (GtkIconInfo *icon_info, gboolean *was_symbolic, GError **error) { - GdkPixbuf *pixbuf; - gchar *css_fg; - gchar *css_success; - gchar *css_warning; - gchar *css_error; + gchar *icon_uri; + gboolean is_symbolic; + g_return_val_if_fail (icon_info != NULL, NULL); g_return_val_if_fail (fg != NULL, NULL); - if (!icon_info->filename || - !g_str_has_suffix (icon_info->filename, "-symbolic.svg")) - { - if (was_symbolic) - *was_symbolic = FALSE; - return gtk_icon_info_load_icon (icon_info, error); - } - - if (was_symbolic) - *was_symbolic = TRUE; - - css_fg = gdk_rgba_to_css (fg); - - css_success = css_warning = css_error = NULL; - - if (warning_color) - css_warning = gdk_rgba_to_css (warning_color); + icon_uri = NULL; + if (icon_info->icon_file) + icon_uri = g_file_get_uri (icon_info->icon_file); - if (error_color) - css_error = gdk_rgba_to_css (error_color); + is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg")); + g_free (icon_uri); - if (success_color) - css_success = gdk_rgba_to_css (success_color); + if (was_symbolic) + *was_symbolic = is_symbolic; - pixbuf = _gtk_icon_info_load_symbolic_internal (icon_info, - css_fg, css_success, - css_warning, css_error, - error); - g_free (css_fg); - g_free (css_warning); - g_free (css_success); - g_free (css_error); + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); - return pixbuf; + return _gtk_icon_info_load_symbolic_internal (icon_info, + fg, success_color, + warning_color, error_color, + TRUE, + error); } /** @@ -3258,54 +4008,392 @@ gtk_icon_info_load_symbolic_for_context (GtkIconInfo *icon_info, gboolean *was_symbolic, GError **error) { - GdkPixbuf *pixbuf; GdkRGBA *color = NULL; - GdkRGBA rgba; - gchar *css_fg = NULL, *css_success; - gchar *css_warning, *css_error; + GdkRGBA fg; + GdkRGBA *fgp; + GdkRGBA success_color; + GdkRGBA *success_colorp; + GdkRGBA warning_color; + GdkRGBA *warning_colorp; + GdkRGBA error_color; + GdkRGBA *error_colorp; GtkStateFlags state; + gchar *icon_uri; + gboolean is_symbolic; + + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + + icon_uri = NULL; + if (icon_info->icon_file) + icon_uri = g_file_get_uri (icon_info->icon_file); + + is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg")); + g_free (icon_uri); + + if (was_symbolic) + *was_symbolic = is_symbolic; + + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); + + fgp = success_colorp = warning_colorp = error_colorp = NULL; - if (!icon_info->filename || - !g_str_has_suffix (icon_info->filename, "-symbolic.svg")) + state = gtk_style_context_get_state (context); + gtk_style_context_get (context, state, "color", &color, NULL); + if (color) { - if (was_symbolic) - *was_symbolic = FALSE; - return gtk_icon_info_load_icon (icon_info, error); + fg = *color; + fgp = &fg; + gdk_rgba_free (color); } + if (gtk_style_context_lookup_color (context, "success_color", &success_color)) + success_colorp = &success_color; + + if (gtk_style_context_lookup_color (context, "warning_color", &warning_color)) + warning_colorp = &warning_color; + + if (gtk_style_context_lookup_color (context, "error_color", &error_color)) + error_colorp = &error_color; + + return _gtk_icon_info_load_symbolic_internal (icon_info, + fgp, success_colorp, + warning_colorp, error_colorp, + TRUE, + error); +} + +typedef struct { + gboolean is_symbolic; + GtkIconInfo *dup; + GdkRGBA fg; + gboolean fg_set; + GdkRGBA success_color; + gboolean success_color_set; + GdkRGBA warning_color; + gboolean warning_color_set; + GdkRGBA error_color; + gboolean error_color_set; +} AsyncSymbolicData; + +static void +async_symbolic_data_free (AsyncSymbolicData *data) +{ + if (data->dup) + g_object_unref (data->dup); + g_slice_free (AsyncSymbolicData, data); +} + +static void +async_load_no_symbolic_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkIconInfo *icon_info = GTK_ICON_INFO (source_object); + GTask *task = user_data; + GError *error = NULL; + GdkPixbuf *pixbuf; + + pixbuf = gtk_icon_info_load_icon_finish (icon_info, res, &error); + if (pixbuf == NULL) + g_task_return_error (task, error); + else + g_task_return_pointer (task, pixbuf, g_object_unref); + g_object_unref (task); +} + +static void +load_symbolic_icon_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + AsyncSymbolicData *data = task_data; + GError *error; + GdkPixbuf *pixbuf; + + error = NULL; + pixbuf = + _gtk_icon_info_load_symbolic_internal (data->dup, + data->fg_set ? &data->fg : NULL, + data->success_color_set ? &data->success_color : NULL, + data->warning_color_set ? &data->warning_color : NULL, + data->error_color_set ? &data->error_color : NULL, + FALSE, + &error); + if (pixbuf == NULL) + g_task_return_error (task, error); + else + g_task_return_pointer (task, pixbuf, g_object_unref); +} + +/** + * gtk_icon_info_load_symbolic_async: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @fg: a #GdkRGBA representing the foreground color of the icon + * @success_color: (allow-none): a #GdkRGBA representing the warning color + * of the icon or %NULL to use the default color + * @warning_color: (allow-none): a #GdkRGBA representing the warning color + * of the icon or %NULL to use the default color + * @error_color: (allow-none): a #GdkRGBA representing the error color + * of the icon or %NULL to use the default color (allow-none) + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call when the + * request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Asynchronously load, render and scale a symbolic icon previously looked up + * from the icon theme using gtk_icon_theme_lookup_icon(). + * + * For more details, see gtk_icon_info_load_symbolic() which is the synchronous + * version of this call. + * + * Since: 3.8 + **/ +void +gtk_icon_info_load_symbolic_async (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + AsyncSymbolicData *data; + gchar *icon_uri; + SymbolicPixbufCache *symbolic_cache; + GdkPixbuf *pixbuf; + + g_return_if_fail (icon_info != NULL); + g_return_if_fail (fg != NULL); + + task = g_task_new (icon_info, cancellable, callback, user_data); + + data = g_slice_new0 (AsyncSymbolicData); + g_task_set_task_data (task, data, (GDestroyNotify) async_symbolic_data_free); + + icon_uri = NULL; + if (icon_info->icon_file) + icon_uri = g_file_get_uri (icon_info->icon_file); + + data->is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg")); + g_free (icon_uri); + + if (!data->is_symbolic) + { + gtk_icon_info_load_icon_async (icon_info, cancellable, async_load_no_symbolic_cb, g_object_ref (task)); + } + else + { + symbolic_cache = symbolic_pixbuf_cache_matches (icon_info->symbolic_pixbuf_cache, + fg, success_color, warning_color, error_color); + if (symbolic_cache) + { + pixbuf = symbolic_cache_get_proxy (symbolic_cache, icon_info); + g_task_return_pointer (task, pixbuf, g_object_unref); + } + else + { + if (fg) + { + data->fg = *fg; + data->fg_set = TRUE; + } + + if (success_color) + { + data->success_color = *success_color; + data->success_color_set = TRUE; + } + + if (warning_color) + { + data->warning_color = *warning_color; + data->warning_color_set = TRUE; + } + + if (error_color) + { + data->error_color = *error_color; + data->error_color_set = TRUE; + } + + data->dup = icon_info_dup (icon_info); + g_task_run_in_thread (task, load_symbolic_icon_thread); + } + } + g_object_unref (task); +} + +/** + * gtk_icon_info_load_symbolic_finish: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @res: a #GAsyncResult + * @was_symbolic: (out) (allow-none): a #gboolean, returns whether the + * loaded icon was a symbolic one and whether the @fg color was + * applied to it. + * @error: (allow-none): location to store error information on failure, + * or %NULL. + * + * Finishes an async icon load, see gtk_icon_info_load_symbolic_async(). + * + * Return value: (transfer full): the rendered icon; this may be a newly + * created icon or a new reference to an internal icon, so you must + * not modify the icon. Use g_object_unref() to release your reference + * to the icon. + * + * Since: 3.8 + **/ +GdkPixbuf * +gtk_icon_info_load_symbolic_finish (GtkIconInfo *icon_info, + GAsyncResult *result, + gboolean *was_symbolic, + GError **error) +{ + GTask *task = G_TASK (result); + AsyncSymbolicData *data = g_task_get_task_data (task); + SymbolicPixbufCache *symbolic_cache; + GdkPixbuf *pixbuf; + if (was_symbolic) - *was_symbolic = TRUE; + *was_symbolic = data->is_symbolic; + + if (data->dup && !g_task_had_error (task)) + { + pixbuf = g_task_propagate_pointer (task, NULL); + + g_assert (pixbuf != NULL); /* we checked for !had_error above */ + + symbolic_cache = symbolic_pixbuf_cache_matches (icon_info->symbolic_pixbuf_cache, + data->fg_set ? &data->fg : NULL, + data->success_color_set ? &data->success_color : NULL, + data->warning_color_set ? &data->warning_color : NULL, + data->error_color_set ? &data->error_color : NULL); + + if (symbolic_cache == NULL) + { + symbolic_cache = icon_info->symbolic_pixbuf_cache = + symbolic_pixbuf_cache_new (pixbuf, + data->fg_set ? &data->fg : NULL, + data->success_color_set ? &data->success_color : NULL, + data->warning_color_set ? &data->warning_color : NULL, + data->error_color_set ? &data->error_color : NULL, + icon_info->symbolic_pixbuf_cache); + } + + g_object_unref (pixbuf); + + return symbolic_cache_get_proxy (symbolic_cache, icon_info); + } + + return g_task_propagate_pointer (task, error); +} + +/** + * gtk_icon_info_load_symbolic_for_context_async: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @context: a #GtkStyleContext + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call when the + * request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Asynchronously load, render and scale a symbolic icon previously looked up + * from the icon theme using gtk_icon_theme_lookup_icon(). + * + * For more details, see gtk_icon_info_load_symbolic_for_context() which is the synchronous + * version of this call. + * + * Since: 3.8 + **/ +void +gtk_icon_info_load_symbolic_for_context_async (GtkIconInfo *icon_info, + GtkStyleContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GdkRGBA *color = NULL; + GdkRGBA fg; + GdkRGBA *fgp; + GdkRGBA success_color; + GdkRGBA *success_colorp; + GdkRGBA warning_color; + GdkRGBA *warning_colorp; + GdkRGBA error_color; + GdkRGBA *error_colorp; + GtkStateFlags state; + + g_return_if_fail (icon_info != NULL); + g_return_if_fail (context != NULL); + + fgp = success_colorp = warning_colorp = error_colorp = NULL; state = gtk_style_context_get_state (context); gtk_style_context_get (context, state, "color", &color, NULL); if (color) { - css_fg = gdk_rgba_to_css (color); + fg = *color; + fgp = &fg; gdk_rgba_free (color); } - css_success = css_warning = css_error = NULL; - - if (gtk_style_context_lookup_color (context, "success_color", &rgba)) - css_success = gdk_rgba_to_css (&rgba); + if (gtk_style_context_lookup_color (context, "success_color", &success_color)) + success_colorp = &success_color; - if (gtk_style_context_lookup_color (context, "warning_color", &rgba)) - css_warning = gdk_rgba_to_css (&rgba); + if (gtk_style_context_lookup_color (context, "warning_color", &warning_color)) + warning_colorp = &warning_color; - if (gtk_style_context_lookup_color (context, "error_color", &rgba)) - css_error = gdk_rgba_to_css (&rgba); + if (gtk_style_context_lookup_color (context, "error_color", &error_color)) + error_colorp = &error_color; - pixbuf = _gtk_icon_info_load_symbolic_internal (icon_info, - css_fg, css_success, - css_warning, css_error, - error); + gtk_icon_info_load_symbolic_async (icon_info, + fgp, success_colorp, + warning_colorp, error_colorp, + cancellable, callback, user_data); +} - g_free (css_fg); - g_free (css_success); - g_free (css_warning); - g_free (css_error); +/** + * gtk_icon_info_load_symbolic_for_context_finish: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @res: a #GAsyncResult + * @was_symbolic: (out) (allow-none): a #gboolean, returns whether the + * loaded icon was a symbolic one and whether the @fg color was + * applied to it. + * @error: (allow-none): location to store error information on failure, + * or %NULL. + * + * Finishes an async icon load, see gtk_icon_info_load_symbolic_for_context_async(). + * + * Return value: (transfer full): the rendered icon; this may be a newly + * created icon or a new reference to an internal icon, so you must + * not modify the icon. Use g_object_unref() to release your reference + * to the icon. + * + * Since: 3.8 + **/ +GdkPixbuf * +gtk_icon_info_load_symbolic_for_context_finish (GtkIconInfo *icon_info, + GAsyncResult *result, + gboolean *was_symbolic, + GError **error) +{ + return gtk_icon_info_load_symbolic_finish (icon_info, result, was_symbolic, error); +} - return pixbuf; +static GdkRGBA * +color_to_rgba (GdkColor *color, GdkRGBA *rgba) +{ + rgba->red = color->red / 65535.0; + rgba->green = color->green / 65535.0; + rgba->blue = color->blue / 65535.0; + rgba->alpha = 1.0; + return rgba; } /** @@ -3340,50 +4428,51 @@ gtk_icon_info_load_symbolic_for_style (GtkIconInfo *icon_info, gboolean *was_symbolic, GError **error) { - GdkPixbuf *pixbuf; - GdkColor success_color; - GdkColor warning_color; - GdkColor error_color; - GdkColor *fg; - gchar *css_fg, *css_success; - gchar *css_warning, *css_error; + GdkColor color; + GdkRGBA fg; + GdkRGBA success_color; + GdkRGBA *success_colorp; + GdkRGBA warning_color; + GdkRGBA *warning_colorp; + GdkRGBA error_color; + GdkRGBA *error_colorp; + gchar *icon_uri; + gboolean is_symbolic; - if (!icon_info->filename || - !g_str_has_suffix (icon_info->filename, "-symbolic.svg")) - { - if (was_symbolic) - *was_symbolic = FALSE; - return gtk_icon_info_load_icon (icon_info, error); - } + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (style != NULL, NULL); - if (was_symbolic) - *was_symbolic = TRUE; + icon_uri = NULL; + if (icon_info->icon_file) + icon_uri = g_file_get_uri (icon_info->icon_file); - fg = &style->fg[state]; - css_fg = gdk_color_to_css (fg); + is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg")); + g_free (icon_uri); - css_success = css_warning = css_error = NULL; + if (was_symbolic) + *was_symbolic = is_symbolic; - if (gtk_style_lookup_color (style, "success_color", &success_color)) - css_success = gdk_color_to_css (&success_color); + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); - if (gtk_style_lookup_color (style, "warning_color", &warning_color)) - css_warning = gdk_color_to_css (&warning_color); + color_to_rgba (&style->fg[state], &fg); - if (gtk_style_lookup_color (style, "error_color", &error_color)) - css_error = gdk_color_to_css (&error_color); + success_colorp = warning_colorp = error_colorp = NULL; - pixbuf = _gtk_icon_info_load_symbolic_internal (icon_info, - css_fg, css_success, - css_warning, css_error, - error); + if (gtk_style_lookup_color (style, "success_color", &color)) + success_colorp = color_to_rgba (&color, &success_color); - g_free (css_fg); - g_free (css_success); - g_free (css_warning); - g_free (css_error); + if (gtk_style_lookup_color (style, "warning_color", &color)) + warning_colorp = color_to_rgba (&color, &warning_color); - return pixbuf; + if (gtk_style_lookup_color (style, "error_color", &color)) + error_colorp = color_to_rgba (&color, &error_color); + + return _gtk_icon_info_load_symbolic_internal (icon_info, + &fg, success_colorp, + warning_colorp, error_colorp, + TRUE, + error); } /** @@ -3564,7 +4653,7 @@ gtk_icon_info_get_attach_points (GtkIconInfo *icon_info, * * Since: 2.4 **/ -G_CONST_RETURN gchar * +const gchar * gtk_icon_info_get_display_name (GtkIconInfo *icon_info) { g_return_val_if_fail (icon_info != NULL, NULL); @@ -3745,9 +4834,9 @@ _gtk_icon_theme_check_reload (GdkDisplay *display) * The icon can then be rendered into a pixbuf using * gtk_icon_info_load_icon(). * - * Return value: a #GtkIconInfo structure containing + * Return value: (transfer full): a #GtkIconInfo structure containing * information about the icon, or %NULL if the icon - * wasn't found. Free with gtk_icon_info_free() + * wasn't found. Unref with g_object_unref() * * Since: 2.14 */ @@ -3767,6 +4856,16 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, info = icon_info_new (); info->loadable = G_LOADABLE_ICON (g_object_ref (icon)); + if (G_IS_FILE_ICON (icon)) + { + GFile *file = g_file_icon_get_file (G_FILE_ICON (icon)); + if (file != NULL) + { + info->icon_file = g_object_ref (file); + info->filename = g_file_get_path (file); + } + } + info->dir_type = ICON_THEME_DIR_UNTHEMED; info->dir_size = size; info->desired_size = size; @@ -3788,15 +4887,18 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, { GIcon *base, *emblem; GList *list, *l; - GtkIconInfo *emblem_info; + GtkIconInfo *base_info, *emblem_info; if (GTK_IS_NUMERABLE_ICON (icon)) _gtk_numerable_icon_set_background_icon_size (GTK_NUMERABLE_ICON (icon), size / 2); base = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon)); - info = gtk_icon_theme_lookup_by_gicon (icon_theme, base, size, flags); - if (info) + base_info = gtk_icon_theme_lookup_by_gicon (icon_theme, base, size, flags); + if (base_info) { + info = icon_info_dup (base_info); + g_object_unref (base_info); + list = g_emblemed_icon_get_emblems (G_EMBLEMED_ICON (icon)); for (l = list; l; l = l->next) { @@ -3806,9 +4908,11 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, if (emblem_info) info->emblem_infos = g_slist_prepend (info->emblem_infos, emblem_info); } - } - return info; + return info; + } + else + return NULL; } else if (GDK_IS_PIXBUF (icon)) { @@ -3854,7 +4958,7 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, * * Creates a #GtkIconInfo for a #GdkPixbuf. * - * Returns: a #GtkIconInfo + * Return value: (transfer full): a #GtkIconInfo * * Since: 2.14 */