From 93319e7c11187609d1f57b68b35f2fbaebcc53c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 10 Jan 2023 20:40:21 +0100 Subject: [PATCH] st/icon-theme: Copy GTK3 code in-tree GTK4 changed icon loading significantly, it is now closely tied to snapshots and paintables. This makes a port highly unrealistic, so to avoid staying stuck on GTK3 forever, copy the relevant code into the tree. The code is unmodified except for the include names and replacing some stray tab indentation. It is still full of GTK internals, so it will take a while before we can actually build it. Part-of: --- po/POTFILES.in | 1 + src/st/st-icon-cache.c | 542 +++ src/st/st-icon-cache.h | 51 + src/st/st-icon-theme-private.h | 41 + src/st/st-icon-theme.c | 5678 ++++++++++++++++++++++++++++++++ src/st/st-icon-theme.h | 376 +++ 6 files changed, 6689 insertions(+) create mode 100644 src/st/st-icon-cache.c create mode 100644 src/st/st-icon-cache.h create mode 100644 src/st/st-icon-theme-private.h create mode 100644 src/st/st-icon-theme.c create mode 100644 src/st/st-icon-theme.h diff --git a/po/POTFILES.in b/po/POTFILES.in index b647698e4..ac35c70ea 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -85,6 +85,7 @@ src/shell-global.c src/shell-keyring-prompt.c src/shell-polkit-authentication-agent.c src/shell-util.c +src/st/st-icon-theme.c subprojects/extensions-app/data/metainfo/org.gnome.Extensions.metainfo.xml.in subprojects/extensions-app/data/org.gnome.Extensions.desktop.in.in subprojects/extensions-app/js/main.js diff --git a/src/st/st-icon-cache.c b/src/st/st-icon-cache.c new file mode 100644 index 000000000..73aff76d2 --- /dev/null +++ b/src/st/st-icon-cache.c @@ -0,0 +1,542 @@ +/* gtkiconcache.c + * Copyright (C) 2004 Anders Carlsson + * + * 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 "config.h" + +#include "st-icon-cache.h" + +#include "gtkdebug.h" +#include "gtkiconcachevalidator.h" + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef G_OS_WIN32 +#include +#endif +#include +#include +#include +#include + + +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 0 + +#define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset)))) +#define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset)))) + + +struct _GtkIconCache { + gint ref_count; + + GMappedFile *map; + gchar *buffer; + + guint32 last_chain_offset; +}; + +GtkIconCache * +_gtk_icon_cache_ref (GtkIconCache *cache) +{ + cache->ref_count++; + return cache; +} + +void +_gtk_icon_cache_unref (GtkIconCache *cache) +{ + cache->ref_count --; + + if (cache->ref_count == 0) + { + GTK_NOTE (ICONTHEME, g_message ("unmapping icon cache")); + + if (cache->map) + g_mapped_file_unref (cache->map); + g_free (cache); + } +} + +GtkIconCache * +_gtk_icon_cache_new_for_path (const gchar *path) +{ + GtkIconCache *cache = NULL; + GMappedFile *map; + + gchar *cache_filename; + gint fd = -1; + GStatBuf st; + GStatBuf path_st; + + /* Check if we have a cache file */ + cache_filename = g_build_filename (path, "icon-theme.cache", NULL); + + GTK_NOTE (ICONTHEME, g_message ("look for icon cache in %s", path)); + + if (g_stat (path, &path_st) < 0) + goto done; + + /* Open the file and map it into memory */ + fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0); + + if (fd < 0) + goto done; + +#ifdef G_OS_WIN32 + +/* Bug 660730: _fstat32 is only defined in msvcrt80.dll+/VS 2005+ */ +/* or possibly in the msvcrt.dll linked to by the Windows DDK */ +/* (will need to check on the Windows DDK part later) */ +#if ((_MSC_VER >= 1400 || __MSVCRT_VERSION__ >= 0x0800) || defined (__MINGW64_VERSION_MAJOR)) && !defined(_WIN64) +#undef fstat /* Just in case */ +#define fstat _fstat32 +#elif defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64) +#undef fstat +#define fstat _fstat64 +#endif +#endif + + if (fstat (fd, &st) < 0 || st.st_size < 4) + goto done; + + /* Verify cache is uptodate */ + if (st.st_mtime < path_st.st_mtime) + { + GTK_NOTE (ICONTHEME, g_message ("icon cache outdated")); + goto done; + } + + map = g_mapped_file_new (cache_filename, FALSE, NULL); + + if (!map) + goto done; + +#ifdef G_ENABLE_DEBUG + if (GTK_DEBUG_CHECK (ICONTHEME)) + { + CacheInfo info; + + info.cache = g_mapped_file_get_contents (map); + info.cache_size = g_mapped_file_get_length (map); + info.n_directories = 0; + info.flags = CHECK_OFFSETS|CHECK_STRINGS; + + if (!_gtk_icon_cache_validate (&info)) + { + g_mapped_file_unref (map); + g_warning ("Icon cache '%s' is invalid", cache_filename); + + goto done; + } + } +#endif + + GTK_NOTE (ICONTHEME, g_message ("found icon cache for %s", path)); + + cache = g_new0 (GtkIconCache, 1); + cache->ref_count = 1; + cache->map = map; + cache->buffer = g_mapped_file_get_contents (map); + + done: + g_free (cache_filename); + if (fd >= 0) + close (fd); + + return cache; +} + +GtkIconCache * +_gtk_icon_cache_new (const gchar *data) +{ + GtkIconCache *cache; + + cache = g_new0 (GtkIconCache, 1); + cache->ref_count = 1; + cache->map = NULL; + cache->buffer = (gchar *)data; + + return cache; +} + +static gint +get_directory_index (GtkIconCache *cache, + const gchar *directory) +{ + guint32 dir_list_offset; + gint n_dirs; + gint i; + + dir_list_offset = GET_UINT32 (cache->buffer, 8); + + n_dirs = GET_UINT32 (cache->buffer, dir_list_offset); + + for (i = 0; i < n_dirs; i++) + { + guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i); + gchar *name = cache->buffer + name_offset; + if (strcmp (name, directory) == 0) + return i; + } + + return -1; +} + +gint +_gtk_icon_cache_get_directory_index (GtkIconCache *cache, + const gchar *directory) +{ + return get_directory_index (cache, directory); +} + +static guint +icon_name_hash (gconstpointer key) +{ + const signed char *p = key; + guint32 h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +static gint +find_image_offset (GtkIconCache *cache, + const gchar *icon_name, + gint directory_index) +{ + guint32 hash_offset; + guint32 n_buckets; + guint32 chain_offset; + int hash; + guint32 image_list_offset, n_images; + int i; + + if (!icon_name) + return 0; + + chain_offset = cache->last_chain_offset; + if (chain_offset) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + if (strcmp (name, icon_name) == 0) + goto find_dir; + } + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + hash = icon_name_hash (icon_name) % n_buckets; + + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + if (strcmp (name, icon_name) == 0) + { + cache->last_chain_offset = chain_offset; + goto find_dir; + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + + cache->last_chain_offset = 0; + return 0; + +find_dir: + /* We've found an icon list, now check if we have the right icon in it */ + image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + n_images = GET_UINT32 (cache->buffer, image_list_offset); + + for (i = 0; i < n_images; i++) + { + if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) == + directory_index) + return image_list_offset + 4 + 8 * i; + } + + return 0; +} + +gint +_gtk_icon_cache_get_icon_flags (GtkIconCache *cache, + const gchar *icon_name, + gint directory_index) +{ + guint32 image_offset; + + image_offset = find_image_offset (cache, icon_name, directory_index); + + if (!image_offset) + return 0; + + return GET_UINT16 (cache->buffer, image_offset + 2); +} + +gboolean +_gtk_icon_cache_has_icons (GtkIconCache *cache, + const gchar *directory) +{ + int directory_index; + guint32 hash_offset, n_buckets; + guint32 chain_offset; + guint32 image_list_offset, n_images; + int i, j; + + directory_index = get_directory_index (cache, directory); + + if (directory_index == -1) + return FALSE; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + for (i = 0; i < n_buckets; i++) + { + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i); + while (chain_offset != 0xffffffff) + { + image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + n_images = GET_UINT32 (cache->buffer, image_list_offset); + + for (j = 0; j < n_images; j++) + { + if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) == + directory_index) + return TRUE; + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + } + + return FALSE; +} + +void +_gtk_icon_cache_add_icons (GtkIconCache *cache, + const gchar *directory, + GHashTable *hash_table) +{ + int directory_index; + guint32 hash_offset, n_buckets; + guint32 chain_offset; + guint32 image_list_offset, n_images; + int i, j; + + directory_index = get_directory_index (cache, directory); + + if (directory_index == -1) + return; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + for (i = 0; i < n_buckets; i++) + { + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + n_images = GET_UINT32 (cache->buffer, image_list_offset); + + for (j = 0; j < n_images; j++) + { + if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) == + directory_index) + g_hash_table_insert (hash_table, name, NULL); + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + } +} + +gboolean +_gtk_icon_cache_has_icon (GtkIconCache *cache, + const gchar *icon_name) +{ + guint32 hash_offset; + guint32 n_buckets; + guint32 chain_offset; + gint hash; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + hash = icon_name_hash (icon_name) % n_buckets; + + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + if (strcmp (name, icon_name) == 0) + return TRUE; + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + + return FALSE; +} + +gboolean +_gtk_icon_cache_has_icon_in_directory (GtkIconCache *cache, + const gchar *icon_name, + const gchar *directory) +{ + guint32 hash_offset; + guint32 n_buckets; + guint32 chain_offset; + gint hash; + gboolean found_icon = FALSE; + gint directory_index; + + directory_index = get_directory_index (cache, directory); + + if (directory_index == -1) + return FALSE; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + hash = icon_name_hash (icon_name) % n_buckets; + + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + if (strcmp (name, icon_name) == 0) + { + found_icon = TRUE; + break; + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + + if (found_icon) + { + guint32 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + guint32 n_images = GET_UINT32 (cache->buffer, image_list_offset); + guint32 image_offset = image_list_offset + 4; + gint i; + for (i = 0; i < n_images; i++) + { + guint16 index = GET_UINT16 (cache->buffer, image_offset); + + if (index == directory_index) + return TRUE; + image_offset += 8; + } + } + + return FALSE; +} + +static void +pixbuf_destroy_cb (guchar *pixels, + gpointer data) +{ + GtkIconCache *cache = data; + + _gtk_icon_cache_unref (cache); +} + +GdkPixbuf * +_gtk_icon_cache_get_icon (GtkIconCache *cache, + const gchar *icon_name, + gint directory_index) +{ + guint32 offset, image_data_offset, pixel_data_offset; + guint32 length, type; + GdkPixbuf *pixbuf; + GdkPixdata pixdata; + GError *error = NULL; + + offset = find_image_offset (cache, icon_name, directory_index); + + if (!offset) + return NULL; + + image_data_offset = GET_UINT32 (cache->buffer, offset + 4); + + if (!image_data_offset) + return NULL; + + pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset); + + type = GET_UINT32 (cache->buffer, pixel_data_offset); + + if (type != 0) + { + GTK_NOTE (ICONTHEME, g_message ("invalid pixel data type %u", type)); + return NULL; + } + + length = GET_UINT32 (cache->buffer, pixel_data_offset + 4); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (!gdk_pixdata_deserialize (&pixdata, length, + (guchar *)(cache->buffer + pixel_data_offset + 8), + &error)) + { + GTK_NOTE (ICONTHEME, g_message ("could not deserialize data: %s", error->message)); + g_error_free (error); + + return NULL; + } +G_GNUC_END_IGNORE_DEPRECATIONS + + pixbuf = gdk_pixbuf_new_from_data (pixdata.pixel_data, GDK_COLORSPACE_RGB, + (pixdata.pixdata_type & GDK_PIXDATA_COLOR_TYPE_MASK) == GDK_PIXDATA_COLOR_TYPE_RGBA, + 8, pixdata.width, pixdata.height, pixdata.rowstride, + (GdkPixbufDestroyNotify)pixbuf_destroy_cb, + cache); + if (!pixbuf) + { + GTK_NOTE (ICONTHEME, g_message ("could not convert pixdata to pixbuf: %s", error->message)); + g_error_free (error); + + return NULL; + } + + _gtk_icon_cache_ref (cache); + + return pixbuf; +} + diff --git a/src/st/st-icon-cache.h b/src/st/st-icon-cache.h new file mode 100644 index 000000000..47a5063ac --- /dev/null +++ b/src/st/st-icon-cache.h @@ -0,0 +1,51 @@ +/* gtkiconcache.h + * Copyright (C) 2004 Anders Carlsson + * + * 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 . + */ +#ifndef __GTK_ICON_CACHE_H__ +#define __GTK_ICON_CACHE_H__ + +#include +#include + +typedef struct _GtkIconCache GtkIconCache; + +GtkIconCache *_gtk_icon_cache_new (const gchar *data); +GtkIconCache *_gtk_icon_cache_new_for_path (const gchar *path); +gint _gtk_icon_cache_get_directory_index (GtkIconCache *cache, + const gchar *directory); +gboolean _gtk_icon_cache_has_icon (GtkIconCache *cache, + const gchar *icon_name); +gboolean _gtk_icon_cache_has_icon_in_directory (GtkIconCache *cache, + const gchar *icon_name, + const gchar *directory); +gboolean _gtk_icon_cache_has_icons (GtkIconCache *cache, + const gchar *directory); +void _gtk_icon_cache_add_icons (GtkIconCache *cache, + const gchar *directory, + GHashTable *hash_table); + +gint _gtk_icon_cache_get_icon_flags (GtkIconCache *cache, + const gchar *icon_name, + gint directory_index); +GdkPixbuf *_gtk_icon_cache_get_icon (GtkIconCache *cache, + const gchar *icon_name, + gint directory_index); + +GtkIconCache *_gtk_icon_cache_ref (GtkIconCache *cache); +void _gtk_icon_cache_unref (GtkIconCache *cache); + + +#endif /* __GTK_ICON_CACHE_H__ */ diff --git a/src/st/st-icon-theme-private.h b/src/st/st-icon-theme-private.h new file mode 100644 index 000000000..8a32cc511 --- /dev/null +++ b/src/st/st-icon-theme-private.h @@ -0,0 +1,41 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2015 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * 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, see . + */ + +#ifndef __GTK_ICON_THEME_PRIVATE_H__ +#define __GTK_ICON_THEME_PRIVATE_H__ + +#include "st-icon-theme.h" +#include + +void gtk_icon_theme_lookup_symbolic_colors (GtkCssStyle *style, + GdkRGBA *color_out, + GdkRGBA *success_out, + GdkRGBA *warning_out, + GdkRGBA *error_out); + +GtkIconInfo *gtk_icon_info_new_for_file (GFile *file, + gint size, + gint scale); + +GdkPixbuf * gtk_icon_theme_color_symbolic_pixbuf (GdkPixbuf *symbolic, + const GdkRGBA *fg_color, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color); + + +#endif /* __GTK_ICON_THEME_PRIVATE_H__ */ diff --git a/src/st/st-icon-theme.c b/src/st/st-icon-theme.c new file mode 100644 index 000000000..deedc9add --- /dev/null +++ b/src/st/st-icon-theme.c @@ -0,0 +1,5678 @@ +/* GtkIconTheme - a loader for icon themes + * gtk-icon-theme.c Copyright (C) 2002, 2003 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * 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, see . + */ + +#include "config.h" + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +#ifndef S_ISDIR +#define S_ISDIR(mode) ((mode)&_S_IFDIR) +#endif +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "win32/gdkwin32.h" +#endif /* G_OS_WIN32 */ + +#include "st-icon-theme-private.h" +#include "gtkcsspalettevalueprivate.h" +#include "gtkcssrgbavalueprivate.h" +#include "gtkdebug.h" +#include "deprecated/gtkiconfactory.h" +#include "st-icon-cache.h" +#include "gtkintl.h" +#include "gtkmain.h" +#include "deprecated/gtknumerableiconprivate.h" +#include "gtksettingsprivate.h" +#include "gtkstylecontextprivate.h" +#include "gtkprivate.h" +#include "gdkpixbufutilsprivate.h" + +#undef GDK_DEPRECATED +#undef GDK_DEPRECATED_FOR +#define GDK_DEPRECATED +#define GDK_DEPRECATED_FOR(f) + +#include "deprecated/gtkstyle.h" + +/* this is in case round() is not provided by the compiler, + * such as in the case of C89 compilers, like MSVC + */ +#include "fallback-c89.c" + +/** + * 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](http://www.freedesktop.org/Standards/icon-theme-spec) + * There is a fallback icon theme, named `hicolor`, where applications + * should install their icons, but additional icon themes can be installed + * as operating system vendors and users choose. + * + * Named icons are similar to the deprecated [Stock Items][gtkstock], + * 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 + * [Stock Items][gtkstock], 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. + * |[ + * 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, // icon 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 FALLBACK_ICON_THEME "hicolor" + +typedef enum +{ + ICON_THEME_DIR_FIXED, + ICON_THEME_DIR_SCALABLE, + ICON_THEME_DIR_THRESHOLD, + ICON_THEME_DIR_UNTHEMED +} IconThemeDirType; + +/* In reverse search order: */ +typedef enum +{ + ICON_SUFFIX_NONE = 0, + ICON_SUFFIX_XPM = 1 << 0, + ICON_SUFFIX_SVG = 1 << 1, + ICON_SUFFIX_PNG = 1 << 2, + HAS_ICON_FILE = 1 << 3, + ICON_SUFFIX_SYMBOLIC_PNG = 1 << 4 +} 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 **search_path; + gint search_path_len; + GList *resource_paths; + + guint custom_theme : 1; + guint is_screen_singleton : 1; + guint pixbuf_supports_svg : 1; + guint themes_valid : 1; + guint loading_themes : 1; + + /* A list of all the themes needed to look up icons. + * In search order, without duplicates + */ + GList *themes; + GHashTable *unthemed_icons; + + /* GdkScreen for the icon theme (may be NULL) */ + GdkScreen *screen; + + /* time when we last stat:ed for theme changes */ + glong last_stat_time; + GList *dir_mtimes; + + gulong theme_changed_idle; +}; + +typedef struct { + gchar **icon_names; + gint size; + gint scale; + 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; + + /* Cache pixbuf (if there is any) */ + GdkPixbuf *cache_pixbuf; + + /* Information about the directory where + * the source was found + */ + IconThemeDirType dir_type; + gint dir_size; + gint dir_scale; + gint min_size; + gint max_size; + + /* Parameters influencing the scaled icon + */ + gint desired_size; + gint desired_scale; + guint forced_size : 1; + guint emblems_applied : 1; + guint is_svg : 1; + guint is_resource : 1; + + /* Cached information if we go ahead and try to load + * the icon. + */ + GdkPixbuf *pixbuf; + GdkPixbuf *proxy_pixbuf; + GError *load_error; + gdouble unscaled_scale; + gdouble scale; + + SymbolicPixbufCache *symbolic_pixbuf_cache; + + gint symbolic_width; + gint symbolic_height; +}; + +typedef struct +{ + gchar *name; + gchar *display_name; + gchar *comment; + gchar *example; + + /* In search order */ + GList *dirs; +} IconTheme; + +typedef struct +{ + IconThemeDirType type; + GQuark context; + + gint size; + gint min_size; + gint max_size; + gint threshold; + gint scale; + gboolean is_resource; + + gchar *dir; + gchar *subdir; + gint subdir_index; + + GtkIconCache *cache; + + GHashTable *icons; +} IconThemeDir; + +typedef struct +{ + gchar *svg_filename; + gchar *no_svg_filename; + gboolean is_resource; +} UnthemedIcon; + +typedef struct +{ + gint size; + GdkPixbuf *pixbuf; +} BuiltinIcon; + +typedef struct +{ + gchar *dir; + time_t mtime; + GtkIconCache *cache; + gboolean exists; +} IconThemeDirMtime; + +static void gtk_icon_theme_finalize (GObject *object); +static void theme_dir_destroy (IconThemeDir *dir); +static void theme_destroy (IconTheme *theme); +static GtkIconInfo *theme_lookup_icon (IconTheme *theme, + const gchar *icon_name, + gint size, + gint scale, + gboolean allow_svg, + gboolean use_default_icons); +static void theme_list_icons (IconTheme *theme, + GHashTable *icons, + GQuark context); +static gboolean theme_has_icon (IconTheme *theme, + const gchar *icon_name); +static void theme_list_contexts (IconTheme *theme, + GHashTable *contexts); +static void theme_subdir_load (GtkIconTheme *icon_theme, + IconTheme *theme, + GKeyFile *theme_file, + gchar *subdir); +static void do_theme_change (GtkIconTheme *icon_theme); +static void blow_themes (GtkIconTheme *icon_themes); +static gboolean rescan_themes (GtkIconTheme *icon_themes); +static IconSuffix theme_dir_get_icon_suffix (IconThemeDir *dir, + const gchar *icon_name, + gboolean *has_icon_file); +static GtkIconInfo *icon_info_new (IconThemeDirType type, + gint dir_size, + gint dir_scale); +static GtkIconInfo *icon_info_new_builtin (BuiltinIcon *icon); +static IconSuffix suffix_from_name (const gchar *name); +static BuiltinIcon *find_builtin_icon (const gchar *icon_name, + gint size, + gint scale, + gint *min_difference_p); +static void remove_from_lru_cache (GtkIconTheme *icon_theme, + GtkIconInfo *icon_info); +static gboolean icon_info_ensure_scale_and_pixbuf (GtkIconInfo* icon_info); + +static guint signal_changed = 0; + +static GHashTable *icon_theme_builtin_icons; + +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->scale * 0x1000010; + h ^= key->flags * 0x100000100; + + 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->scale != b->scale) + 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_WITH_PRIVATE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT) + +/** + * gtk_icon_theme_new: + * + * Creates a new icon theme object. Icon theme objects are used + * to lookup up an icon by name in a particular icon theme. + * Usually, you’ll want to use gtk_icon_theme_get_default() + * or gtk_icon_theme_get_for_screen() rather than creating + * a new icon theme object for scratch. + * + * Returns: the newly created #GtkIconTheme object. + * + * Since: 2.4 + */ +GtkIconTheme * +gtk_icon_theme_new (void) +{ + return g_object_new (GTK_TYPE_ICON_THEME, NULL); +} + +/** + * gtk_icon_theme_get_default: + * + * Gets the icon theme for the default screen. See + * gtk_icon_theme_get_for_screen(). + * + * Returns: (transfer none): A unique #GtkIconTheme associated with + * the default screen. This icon theme is associated with + * the screen and can be used as long as the screen + * is open. Do not ref or unref it. + * + * Since: 2.4 + */ +GtkIconTheme * +gtk_icon_theme_get_default (void) +{ + return gtk_icon_theme_get_for_screen (gdk_screen_get_default ()); +} + +/** + * gtk_icon_theme_get_for_screen: + * @screen: a #GdkScreen + * + * Gets the icon theme object associated with @screen; if this + * function has not previously been called for the given + * screen, a new icon theme object will be created and + * associated with the screen. Icon theme objects are + * fairly expensive to create, so using this function + * is usually a better choice than calling than gtk_icon_theme_new() + * and setting the screen yourself; by using this function + * a single icon theme object will be shared between users. + * + * Returns: (transfer none): A unique #GtkIconTheme associated with + * the given screen. This icon theme is associated with + * the screen and can be used as long as the screen + * is open. Do not ref or unref it. + * + * Since: 2.4 + */ +GtkIconTheme * +gtk_icon_theme_get_for_screen (GdkScreen *screen) +{ + GtkIconTheme *icon_theme; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + icon_theme = g_object_get_data (G_OBJECT (screen), "gtk-icon-theme"); + if (!icon_theme) + { + GtkIconThemePrivate *priv; + + icon_theme = gtk_icon_theme_new (); + gtk_icon_theme_set_screen (icon_theme, screen); + + priv = icon_theme->priv; + priv->is_screen_singleton = TRUE; + + g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), icon_theme); + } + + return icon_theme; +} + +static void +gtk_icon_theme_class_init (GtkIconThemeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gtk_icon_theme_finalize; + + /** + * GtkIconTheme::changed: + * @icon_theme: the icon theme + * + * Emitted when the current icon theme is switched or GTK+ detects + * that a change has occurred in the contents of the current + * icon theme. + */ + signal_changed = g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkIconThemeClass, changed), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); +} + + +/* Callback when the display that the icon theme is attached + * to is closed; unset the screen, and if it’s the unique theme + * for the screen, drop the reference + */ +static void +display_closed (GdkDisplay *display, + gboolean is_error, + GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + GdkScreen *screen = priv->screen; + gboolean was_screen_singleton = priv->is_screen_singleton; + + if (was_screen_singleton) + { + g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), NULL); + priv->is_screen_singleton = FALSE; + } + + gtk_icon_theme_set_screen (icon_theme, NULL); + + if (was_screen_singleton) + { + g_object_unref (icon_theme); + } +} + +static void +update_current_theme (GtkIconTheme *icon_theme) +{ +#define theme_changed(_old, _new) \ + ((_old && !_new) || (!_old && _new) || \ + (_old && _new && strcmp (_old, _new) != 0)) + GtkIconThemePrivate *priv = icon_theme->priv; + + if (!priv->custom_theme) + { + gchar *theme = NULL; + gboolean changed = FALSE; + + if (priv->screen) + { + GtkSettings *settings = gtk_settings_get_for_screen (priv->screen); + g_object_get (settings, "gtk-icon-theme-name", &theme, NULL); + } + + if (theme_changed (priv->current_theme, theme)) + { + g_free (priv->current_theme); + priv->current_theme = theme; + changed = TRUE; + } + else + g_free (theme); + + if (changed) + do_theme_change (icon_theme); + } +#undef theme_changed +} + +/* Callback when the icon theme GtkSetting changes + */ +static void +theme_changed (GtkSettings *settings, + GParamSpec *pspec, + GtkIconTheme *icon_theme) +{ + update_current_theme (icon_theme); +} + +static void +unset_screen (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + GtkSettings *settings; + GdkDisplay *display; + + if (priv->screen) + { + settings = gtk_settings_get_for_screen (priv->screen); + display = gdk_screen_get_display (priv->screen); + + g_signal_handlers_disconnect_by_func (display, + (gpointer) display_closed, + icon_theme); + if (settings) + g_signal_handlers_disconnect_by_func (settings, + (gpointer) theme_changed, + icon_theme); + + priv->screen = NULL; + } +} + +/** + * gtk_icon_theme_set_screen: + * @icon_theme: a #GtkIconTheme + * @screen: a #GdkScreen + * + * Sets the screen for an icon theme; the screen is used + * to track the user’s currently configured icon theme, + * which might be different for different screens. + * + * Since: 2.4 + */ +void +gtk_icon_theme_set_screen (GtkIconTheme *icon_theme, + GdkScreen *screen) +{ + GtkIconThemePrivate *priv; + GtkSettings *settings; + GdkDisplay *display; + + g_return_if_fail (GTK_ICON_THEME (icon_theme)); + g_return_if_fail (screen == NULL || GDK_IS_SCREEN (screen)); + + priv = icon_theme->priv; + + unset_screen (icon_theme); + + if (screen) + { + display = gdk_screen_get_display (screen); + settings = gtk_settings_get_for_screen (screen); + + priv->screen = screen; + + g_signal_connect (display, "closed", + G_CALLBACK (display_closed), icon_theme); + g_signal_connect (settings, "notify::gtk-icon-theme-name", + G_CALLBACK (theme_changed), icon_theme); + } + + update_current_theme (icon_theme); +} + +/* Checks whether a loader for SVG files has been registered + * with GdkPixbuf. + */ +static gboolean +pixbuf_supports_svg (void) +{ + GSList *formats; + GSList *tmp_list; + static gint found_svg = -1; + + if (found_svg != -1) + return found_svg; + + formats = gdk_pixbuf_get_formats (); + + found_svg = FALSE; + for (tmp_list = formats; tmp_list && !found_svg; tmp_list = tmp_list->next) + { + gchar **mime_types = gdk_pixbuf_format_get_mime_types (tmp_list->data); + gchar **mime_type; + + for (mime_type = mime_types; *mime_type && !found_svg; mime_type++) + { + if (strcmp (*mime_type, "image/svg") == 0) + found_svg = TRUE; + } + + g_strfreev (mime_types); + } + + g_slist_free (formats); + + 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) +{ + GtkIconThemePrivate *priv; + const gchar * const *xdg_data_dirs; + int i, j; + + priv = gtk_icon_theme_get_instance_private (icon_theme); + 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 (); + for (i = 0; xdg_data_dirs[i]; i++) ; + + priv->search_path_len = 2 * i + 2; + + priv->search_path = g_new (char *, priv->search_path_len); + + i = 0; + 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); + + for (j = 0; xdg_data_dirs[j]; j++) + priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "pixmaps", NULL); + + priv->resource_paths = g_list_append (NULL, g_strdup ("/org/gtk/libgtk/icons/")); + + priv->themes_valid = FALSE; + priv->themes = NULL; + priv->unthemed_icons = NULL; + + priv->pixbuf_supports_svg = pixbuf_supports_svg (); +} + +static void +free_dir_mtime (IconThemeDirMtime *dir_mtime) +{ + if (dir_mtime->cache) + _gtk_icon_cache_unref (dir_mtime->cache); + + g_free (dir_mtime->dir); + g_slice_free (IconThemeDirMtime, dir_mtime); +} + +static gboolean +theme_changed_idle (gpointer user_data) +{ + GtkIconTheme *icon_theme; + GtkIconThemePrivate *priv; + + icon_theme = GTK_ICON_THEME (user_data); + priv = icon_theme->priv; + + g_signal_emit (icon_theme, signal_changed, 0); + + if (priv->screen && priv->is_screen_singleton) + gtk_style_context_reset_widgets (priv->screen); + + priv->theme_changed_idle = 0; + + return FALSE; +} + +static void +queue_theme_changed (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + if (!priv->theme_changed_idle) + { + priv->theme_changed_idle = + gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE - 2, + theme_changed_idle, icon_theme, NULL); + g_source_set_name_by_id (priv->theme_changed_idle, "[gtk+] theme_changed_idle"); + } +} + +static void +do_theme_change (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + g_hash_table_remove_all (priv->info_cache); + + if (!priv->themes_valid) + return; + + GTK_NOTE (ICONTHEME, + g_message ("change to icon theme \"%s\"", priv->current_theme)); + blow_themes (icon_theme); + + queue_theme_changed (icon_theme); + +} + +static void +blow_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + if (priv->themes_valid) + { + 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; + priv->unthemed_icons = NULL; + priv->dir_mtimes = NULL; + priv->themes_valid = FALSE; +} + +static void +gtk_icon_theme_finalize (GObject *object) +{ + GtkIconTheme *icon_theme; + GtkIconThemePrivate *priv; + int i; + + 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->theme_changed_idle) + g_source_remove (priv->theme_changed_idle); + + unset_screen (icon_theme); + + g_free (priv->current_theme); + + for (i = 0; i < priv->search_path_len; i++) + g_free (priv->search_path[i]); + g_free (priv->search_path); + + g_list_free_full (priv->resource_paths, g_free); + + blow_themes (icon_theme); + + G_OBJECT_CLASS (gtk_icon_theme_parent_class)->finalize (object); +} + +/** + * gtk_icon_theme_set_search_path: + * @icon_theme: a #GtkIconTheme + * @path: (array length=n_elements) (element-type filename): array of + * directories that are searched for icon themes + * @n_elements: number of elements in @path. + * + * Sets the search path for the icon theme object. When looking + * for an icon theme, GTK+ will search for a subdirectory of + * one or more of the directories in @path with the same name + * as the icon theme containing an index.theme file. (Themes from + * multiple of the path elements are combined to allow themes to be + * extended by adding icons in the user’s home directory.) + * + * In addition if an icon found isn’t found either in the current + * icon theme or the default icon theme, and an image file with + * the right name is found directly in one of the elements of + * @path, then that image will be used for the icon name. + * (This is legacy feature, and new icons should be put + * into the fallback icon theme, which is called hicolor, + * rather than directly on the icon path.) + * + * Since: 2.4 + */ +void +gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme, + const gchar *path[], + gint n_elements) +{ + GtkIconThemePrivate *priv; + gint i; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + + priv = icon_theme->priv; + for (i = 0; i < priv->search_path_len; i++) + g_free (priv->search_path[i]); + + g_free (priv->search_path); + + priv->search_path = g_new (gchar *, n_elements); + priv->search_path_len = n_elements; + + for (i = 0; i < priv->search_path_len; i++) + priv->search_path[i] = g_strdup (path[i]); + + do_theme_change (icon_theme); +} + +/** + * gtk_icon_theme_get_search_path: + * @icon_theme: a #GtkIconTheme + * @path: (allow-none) (array length=n_elements) (element-type filename) (out): + * location to store a list of icon theme path directories or %NULL. + * The stored value should be freed with g_strfreev(). + * @n_elements: location to store number of elements in @path, or %NULL + * + * Gets the current search path. See gtk_icon_theme_set_search_path(). + * + * Since: 2.4 + */ +void +gtk_icon_theme_get_search_path (GtkIconTheme *icon_theme, + gchar **path[], + gint *n_elements) +{ + GtkIconThemePrivate *priv; + gint i; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + + priv = icon_theme->priv; + + if (n_elements) + *n_elements = priv->search_path_len; + + if (path) + { + *path = g_new (gchar *, priv->search_path_len + 1); + for (i = 0; i < priv->search_path_len; i++) + (*path)[i] = g_strdup (priv->search_path[i]); + (*path)[i] = NULL; + } +} + +/** + * gtk_icon_theme_append_search_path: + * @icon_theme: a #GtkIconTheme + * @path: (type filename): directory name to append to the icon path + * + * Appends a directory to the search path. + * See gtk_icon_theme_set_search_path(). + * + * Since: 2.4 + */ +void +gtk_icon_theme_append_search_path (GtkIconTheme *icon_theme, + const gchar *path) +{ + GtkIconThemePrivate *priv; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + g_return_if_fail (path != NULL); + + priv = icon_theme->priv; + + priv->search_path_len++; + + priv->search_path = g_renew (gchar *, priv->search_path, priv->search_path_len); + priv->search_path[priv->search_path_len-1] = g_strdup (path); + + do_theme_change (icon_theme); +} + +/** + * gtk_icon_theme_prepend_search_path: + * @icon_theme: a #GtkIconTheme + * @path: (type filename): directory name to prepend to the icon path + * + * Prepends a directory to the search path. + * See gtk_icon_theme_set_search_path(). + * + * Since: 2.4 + */ +void +gtk_icon_theme_prepend_search_path (GtkIconTheme *icon_theme, + const gchar *path) +{ + GtkIconThemePrivate *priv; + gint i; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + g_return_if_fail (path != NULL); + + priv = icon_theme->priv; + + priv->search_path_len++; + priv->search_path = g_renew (gchar *, priv->search_path, priv->search_path_len); + + for (i = priv->search_path_len - 1; i > 0; i--) + priv->search_path[i] = priv->search_path[i - 1]; + + priv->search_path[0] = g_strdup (path); + + do_theme_change (icon_theme); +} + +/** + * gtk_icon_theme_add_resource_path: + * @icon_theme: a #GtkIconTheme + * @path: a resource path + * + * Adds a resource path that will be looked at when looking + * for icons, similar to search paths. + * + * This function should be used to make application-specific icons + * available as part of the icon theme. + * + * The resources are considered as part of the hicolor icon theme + * and must be located in subdirectories that are defined in the + * hicolor icon theme, such as `@path/16x16/actions/run.png`. + * Icons that are directly placed in the resource path instead + * of a subdirectory are also considered as ultimate fallback. + * + * Since: 3.14 + */ +void +gtk_icon_theme_add_resource_path (GtkIconTheme *icon_theme, + const gchar *path) +{ + GtkIconThemePrivate *priv = NULL; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + g_return_if_fail (path != NULL); + + priv = icon_theme->priv; + priv->resource_paths = g_list_append (priv->resource_paths, g_strdup (path)); + + do_theme_change (icon_theme); +} + +/** + * gtk_icon_theme_set_custom_theme: + * @icon_theme: a #GtkIconTheme + * @theme_name: (allow-none): name of icon theme to use instead of + * configured theme, or %NULL to unset a previously set custom theme + * + * Sets the name of the icon theme that the #GtkIconTheme object uses + * overriding system configuration. This function cannot be called + * on the icon theme objects returned from gtk_icon_theme_get_default() + * and gtk_icon_theme_get_for_screen(). + * + * Since: 2.4 + */ +void +gtk_icon_theme_set_custom_theme (GtkIconTheme *icon_theme, + const gchar *theme_name) +{ + GtkIconThemePrivate *priv; + + g_return_if_fail (GTK_IS_ICON_THEME (icon_theme)); + + priv = icon_theme->priv; + + g_return_if_fail (!priv->is_screen_singleton); + + if (theme_name != NULL) + { + priv->custom_theme = TRUE; + if (!priv->current_theme || strcmp (theme_name, priv->current_theme) != 0) + { + g_free (priv->current_theme); + priv->current_theme = g_strdup (theme_name); + + do_theme_change (icon_theme); + } + } + else + { + if (priv->custom_theme) + { + priv->custom_theme = FALSE; + update_current_theme (icon_theme); + } + } +} + +static const gchar builtin_hicolor_index[] = +"[Icon Theme]\n" +"Name=Hicolor\n" +"Hidden=True\n" +"Directories=16x16/actions,16x16/status,22x22/actions,24x24/actions,24x24/status,32x32/actions,32x32/status,48x48/status,64x64/actions\n" +"[16x16/actions]\n" +"Size=16\n" +"Type=Threshold\n" +"[16x16/status]\n" +"Size=16\n" +"Type=Threshold\n" +"[22x22/actions]\n" +"Size=22\n" +"Type=Threshold\n" +"[24x24/actions]\n" +"Size=24\n" +"Type=Threshold\n" +"[24x24/status]\n" +"Size=24\n" +"Type=Threshold\n" +"[32x32/actions]\n" +"Size=32\n" +"Type=Threshold\n" +"[32x32/status]\n" +"Size=32\n" +"Type=Threshold\n" +"[48x48/status]\n" +"Size=48\n" +"Type=Threshold\n" +"[64x64/actions]\n" +"Size=64\n" +"Type=Threshold\n"; + +static void +insert_theme (GtkIconTheme *icon_theme, + const gchar *theme_name) +{ + gint i; + GList *l; + gchar **dirs; + gchar **scaled_dirs; + gchar **themes; + GtkIconThemePrivate *priv; + IconTheme *theme = NULL; + gchar *path; + GKeyFile *theme_file; + GError *error = NULL; + IconThemeDirMtime *dir_mtime; + GStatBuf stat_buf; + + priv = icon_theme->priv; + + for (l = priv->themes; l != NULL; l = l->next) + { + theme = l->data; + if (strcmp (theme->name, theme_name) == 0) + return; + } + + for (i = 0; i < priv->search_path_len; i++) + { + path = g_build_filename (priv->search_path[i], + theme_name, + NULL); + dir_mtime = g_slice_new (IconThemeDirMtime); + dir_mtime->cache = NULL; + dir_mtime->dir = path; + if (g_stat (path, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode)) { + dir_mtime->mtime = stat_buf.st_mtime; + dir_mtime->exists = TRUE; + } else { + dir_mtime->mtime = 0; + dir_mtime->exists = FALSE; + } + + priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime); + } + + theme_file = NULL; + for (i = 0; i < priv->search_path_len && !theme_file; i++) + { + path = g_build_filename (priv->search_path[i], + theme_name, + "index.theme", + NULL); + if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) + { + theme_file = g_key_file_new (); + g_key_file_set_list_separator (theme_file, ','); + if (!g_key_file_load_from_file (theme_file, path, 0, &error)) + { + g_key_file_free (theme_file); + theme_file = NULL; + g_error_free (error); + error = NULL; + } + } + g_free (path); + } + + if (theme_file || strcmp (theme_name, FALLBACK_ICON_THEME) == 0) + { + theme = g_new0 (IconTheme, 1); + theme->name = g_strdup (theme_name); + priv->themes = g_list_prepend (priv->themes, theme); + if (!theme_file) + { + theme_file = g_key_file_new (); + g_key_file_set_list_separator (theme_file, ','); + g_key_file_load_from_data (theme_file, builtin_hicolor_index, -1, 0, NULL); + } + } + + if (theme_file == NULL) + return; + + theme->display_name = + g_key_file_get_locale_string (theme_file, "Icon Theme", "Name", NULL, NULL); + if (!theme->display_name) + g_warning ("Theme file for %s has no name", theme_name); + + dirs = g_key_file_get_string_list (theme_file, "Icon Theme", "Directories", NULL, NULL); + if (!dirs) + { + g_warning ("Theme file for %s has no directories", theme_name); + priv->themes = g_list_remove (priv->themes, theme); + g_free (theme->name); + g_free (theme->display_name); + g_free (theme); + g_key_file_free (theme_file); + return; + } + + scaled_dirs = g_key_file_get_string_list (theme_file, "Icon Theme", "ScaledDirectories", NULL, NULL); + + theme->comment = + g_key_file_get_locale_string (theme_file, + "Icon Theme", "Comment", + NULL, NULL); + theme->example = + g_key_file_get_string (theme_file, + "Icon Theme", "Example", + NULL); + + theme->dirs = NULL; + for (i = 0; dirs[i] != NULL; i++) + theme_subdir_load (icon_theme, theme, theme_file, dirs[i]); + + if (scaled_dirs) + { + for (i = 0; scaled_dirs[i] != NULL; i++) + theme_subdir_load (icon_theme, theme, theme_file, scaled_dirs[i]); + } + g_strfreev (dirs); + g_strfreev (scaled_dirs); + + theme->dirs = g_list_reverse (theme->dirs); + + themes = g_key_file_get_string_list (theme_file, + "Icon Theme", + "Inherits", + NULL, + NULL); + if (themes) + { + for (i = 0; themes[i] != NULL; i++) + insert_theme (icon_theme, themes[i]); + + g_strfreev (themes); + } + + g_key_file_free (theme_file); +} + +static void +free_unthemed_icon (UnthemedIcon *unthemed_icon) +{ + g_free (unthemed_icon->svg_filename); + g_free (unthemed_icon->no_svg_filename); + g_slice_free (UnthemedIcon, unthemed_icon); +} + +static gchar * +strip_suffix (const gchar *filename) +{ + const gchar *dot; + + if (g_str_has_suffix (filename, ".symbolic.png")) + return g_strndup (filename, strlen(filename)-13); + + dot = strrchr (filename, '.'); + + if (dot == NULL) + return g_strdup (filename); + + return g_strndup (filename, dot - filename); +} + +static void +add_unthemed_icon (GtkIconTheme *icon_theme, + const gchar *dir, + const gchar *file, + gboolean is_resource) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + IconSuffix new_suffix, old_suffix; + gchar *abs_file; + gchar *base_name; + UnthemedIcon *unthemed_icon; + + new_suffix = suffix_from_name (file); + + if (new_suffix == ICON_SUFFIX_NONE) + return; + + abs_file = g_build_filename (dir, file, NULL); + base_name = strip_suffix (file); + + unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, base_name); + + if (unthemed_icon) + { + if (new_suffix == ICON_SUFFIX_SVG) + { + if (unthemed_icon->svg_filename) + g_free (abs_file); + else + unthemed_icon->svg_filename = abs_file; + } + else + { + if (unthemed_icon->no_svg_filename) + { + old_suffix = suffix_from_name (unthemed_icon->no_svg_filename); + if (new_suffix > old_suffix) + { + g_free (unthemed_icon->no_svg_filename); + unthemed_icon->no_svg_filename = abs_file; + } + else + g_free (abs_file); + } + else + unthemed_icon->no_svg_filename = abs_file; + } + + g_free (base_name); + } + else + { + unthemed_icon = g_slice_new0 (UnthemedIcon); + + unthemed_icon->is_resource = is_resource; + + if (new_suffix == ICON_SUFFIX_SVG) + unthemed_icon->svg_filename = abs_file; + else + unthemed_icon->no_svg_filename = abs_file; + + /* takes ownership of base_name */ + g_hash_table_replace (priv->unthemed_icons, base_name, unthemed_icon); + } +} + +static void +load_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + GDir *gdir; + gint base; + gchar *dir; + const gchar *file; + GTimeVal tv; + IconThemeDirMtime *dir_mtime; + GStatBuf stat_buf; + GList *d; + + priv = icon_theme->priv; + + if (priv->current_theme) + insert_theme (icon_theme, priv->current_theme); + + /* Always look in the Adwaita, gnome and hicolor icon themes. + * Looking in hicolor is mandated by the spec, looking in Adwaita + * and gnome is a pragmatic solution to prevent missing icons in + * GTK+ applications when run under, e.g. KDE. + */ + insert_theme (icon_theme, DEFAULT_ICON_THEME); + insert_theme (icon_theme, "gnome"); + insert_theme (icon_theme, FALLBACK_ICON_THEME); + priv->themes = g_list_reverse (priv->themes); + + + priv->unthemed_icons = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)free_unthemed_icon); + + for (base = 0; base < icon_theme->priv->search_path_len; base++) + { + dir = icon_theme->priv->search_path[base]; + + dir_mtime = g_slice_new (IconThemeDirMtime); + priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime); + + dir_mtime->dir = g_strdup (dir); + dir_mtime->mtime = 0; + dir_mtime->exists = FALSE; + dir_mtime->cache = NULL; + + if (g_stat (dir, &stat_buf) != 0 || !S_ISDIR (stat_buf.st_mode)) + continue; + dir_mtime->mtime = stat_buf.st_mtime; + dir_mtime->exists = TRUE; + + dir_mtime->cache = _gtk_icon_cache_new_for_path (dir); + if (dir_mtime->cache != NULL) + continue; + + gdir = g_dir_open (dir, 0, NULL); + if (gdir == NULL) + continue; + + while ((file = g_dir_read_name (gdir))) + add_unthemed_icon (icon_theme, dir, file, FALSE); + + g_dir_close (gdir); + } + priv->dir_mtimes = g_list_reverse (priv->dir_mtimes); + + for (d = priv->resource_paths; d; d = d->next) + { + gchar **children; + gint i; + + dir = d->data; + children = g_resources_enumerate_children (dir, 0, NULL); + if (!children) + continue; + + for (i = 0; children[i]; i++) + add_unthemed_icon (icon_theme, dir, children[i], TRUE); + + g_strfreev (children); + } + + priv->themes_valid = TRUE; + + g_get_current_time (&tv); + priv->last_stat_time = tv.tv_sec; + + GTK_NOTE (ICONTHEME, { + GList *l; + GString *s; + s = g_string_new ("Current icon themes "); + for (l = icon_theme->priv->themes; l; l = l->next) + { + IconTheme *theme = l->data; + g_string_append (s, theme->name); + g_string_append_c (s, ' '); + } + g_message ("%s", s->str); + g_string_free (s, TRUE); + }); +} + +static void +ensure_valid_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + GTimeVal tv; + gboolean was_valid = priv->themes_valid; + + if (priv->loading_themes) + return; + priv->loading_themes = TRUE; + + if (priv->themes_valid) + { + g_get_current_time (&tv); + + if (ABS (tv.tv_sec - priv->last_stat_time) > 5 && + rescan_themes (icon_theme)) + { + g_hash_table_remove_all (priv->info_cache); + blow_themes (icon_theme); + } + } + + if (!priv->themes_valid) + { + load_themes (icon_theme); + + if (was_valid) + queue_theme_changed (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 gboolean +icon_name_is_symbolic (const gchar *icon_name) +{ + return g_str_has_suffix (icon_name, "-symbolic") + || g_str_has_suffix (icon_name, "-symbolic-ltr") + || g_str_has_suffix (icon_name, "-symbolic-rtl"); +} + +static gboolean +icon_uri_is_symbolic (const gchar *icon_name) +{ + return g_str_has_suffix (icon_name, "-symbolic.svg") + || g_str_has_suffix (icon_name, "-symbolic-ltr.svg") + || g_str_has_suffix (icon_name, "-symbolic-rtl.svg") + || g_str_has_suffix (icon_name, ".symbolic.png"); +} + +static GtkIconInfo * +real_choose_icon (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + gint scale, + GtkIconLookupFlags flags) +{ + GtkIconThemePrivate *priv; + GList *l; + GtkIconInfo *icon_info = NULL; + GtkIconInfo *unscaled_icon_info; + UnthemedIcon *unthemed_icon = NULL; + const gchar *icon_name = NULL; + gboolean allow_svg; + gboolean use_builtin; + IconTheme *theme = NULL; + gint i; + IconInfoKey key; + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + key.icon_names = (gchar **)icon_names; + key.size = size; + key.scale = scale; + 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) + allow_svg = TRUE; + else + allow_svg = priv->pixbuf_supports_svg; + + use_builtin = flags & GTK_ICON_LOOKUP_USE_BUILTIN; + + /* This is used in the icontheme unit test */ + GTK_NOTE (ICONTHEME, + for (i = 0; icon_names[i]; i++) + g_message ("\tlookup name: %s", icon_names[i])); + + /* For symbolic icons, do a search in all registered themes first; + * a theme that inherits them from a parent theme might provide + * an alternative full-color version, but still expect the symbolic icon + * to show up instead. + * + * In other words: We prefer symbolic icons in inherited themes over + * generic icons in the theme. + */ + for (l = priv->themes; l; l = l->next) + { + theme = l->data; + for (i = 0; icon_names[i] && icon_name_is_symbolic (icon_names[i]); i++) + { + icon_name = icon_names[i]; + icon_info = theme_lookup_icon (theme, icon_name, size, scale, allow_svg, use_builtin); + if (icon_info) + goto out; + } + } + + for (l = priv->themes; l; l = l->next) + { + theme = l->data; + + for (i = 0; icon_names[i]; i++) + { + icon_name = icon_names[i]; + icon_info = theme_lookup_icon (theme, icon_name, size, scale, allow_svg, use_builtin); + if (icon_info) + goto out; + } + } + + theme = NULL; + + for (i = 0; icon_names[i]; i++) + { + unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, icon_names[i]); + if (unthemed_icon) + break; + } +#ifdef G_OS_WIN32 + /* Still not found an icon, check if reference to a Win32 resource */ + if (!unthemed_icon) + { + gchar **resources; + HICON hIcon = NULL; + + resources = g_strsplit (icon_names[0], ",", 0); + if (resources[0]) + { + wchar_t *wfile = g_utf8_to_utf16 (resources[0], -1, NULL, NULL, NULL); + ExtractIconExW (wfile, resources[1] ? atoi (resources[1]) : 0, &hIcon, NULL, 1); + g_free (wfile); + } + + if (hIcon) + { + icon_info = icon_info_new (ICON_THEME_DIR_UNTHEMED, size, 1); + icon_info->cache_pixbuf = gdk_win32_icon_to_pixbuf_libgtk_only (hIcon, NULL, NULL); + DestroyIcon (hIcon); + } + g_strfreev (resources); + } +#endif + + if (unthemed_icon) + { + icon_info = icon_info_new (ICON_THEME_DIR_UNTHEMED, size, 1); + + /* A SVG icon, when allowed, beats out a XPM icon, but not a PNG icon */ + if (allow_svg && + unthemed_icon->svg_filename && + (!unthemed_icon->no_svg_filename || + suffix_from_name (unthemed_icon->no_svg_filename) < ICON_SUFFIX_PNG)) + icon_info->filename = g_strdup (unthemed_icon->svg_filename); + else if (unthemed_icon->no_svg_filename) + icon_info->filename = g_strdup (unthemed_icon->no_svg_filename); + else + { + static gboolean warned_once = FALSE; + + if (!warned_once) + { + g_warning ("Found an icon but could not load it. " + "Most likely gdk-pixbuf does not provide SVG support."); + warned_once = TRUE; + } + + g_clear_object (&icon_info); + goto out; + } + + if (unthemed_icon->is_resource) + { + gchar *uri; + uri = g_strconcat ("resource://", icon_info->filename, NULL); + icon_info->icon_file = g_file_new_for_uri (uri); + g_free (uri); + } + else + icon_info->icon_file = g_file_new_for_path (icon_info->filename); + + icon_info->is_svg = suffix_from_name (icon_info->filename) == ICON_SUFFIX_SVG; + icon_info->is_resource = unthemed_icon->is_resource; + } + + out: + if (icon_info) + { + icon_info->desired_size = size; + icon_info->desired_scale = scale; + icon_info->forced_size = (flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0; + + /* In case we're not scaling the icon we want to reuse the exact same + * size as a scale==1 lookup would be, rather than not scaling at all + * and causing a different layout + */ + icon_info->unscaled_scale = 1.0; + if (scale != 1 && !icon_info->forced_size && theme != NULL) + { + unscaled_icon_info = theme_lookup_icon (theme, icon_name, size, 1, allow_svg, use_builtin); + if (unscaled_icon_info) + { + icon_info->unscaled_scale = + (gdouble) unscaled_icon_info->dir_size * scale / (icon_info->dir_size * icon_info->dir_scale); + g_object_unref (unscaled_icon_info); + } + } + + icon_info->key.icon_names = g_strdupv ((char **)icon_names); + icon_info->key.size = size; + icon_info->key.scale = scale; + 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 + { + static gboolean check_for_default_theme = TRUE; + gchar *default_theme_path; + gboolean found = FALSE; + + if (check_for_default_theme) + { + check_for_default_theme = FALSE; + + for (i = 0; !found && i < priv->search_path_len; i++) + { + default_theme_path = g_build_filename (priv->search_path[i], + FALLBACK_ICON_THEME, + "index.theme", + NULL); + found = g_file_test (default_theme_path, G_FILE_TEST_IS_REGULAR); + g_free (default_theme_path); + } + + 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", + icon_names[0], FALLBACK_ICON_THEME, "http://icon-theme.freedesktop.org/releases"); + } + } + } + + return icon_info; +} + +static void +icon_name_list_add_icon (GPtrArray *icons, + const gchar *dir_suffix, + gchar *icon_name) +{ + if (dir_suffix) + g_ptr_array_add (icons, g_strconcat (icon_name, dir_suffix, NULL)); + g_ptr_array_add (icons, icon_name); +} + +static GtkIconInfo * +choose_icon (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + gint scale, + GtkIconLookupFlags flags) +{ + gboolean has_regular = FALSE, has_symbolic = FALSE; + GtkIconInfo *icon_info; + GPtrArray *new_names; + const gchar *dir_suffix; + guint i; + + if (flags & GTK_ICON_LOOKUP_DIR_LTR) + dir_suffix = "-ltr"; + else if (flags & GTK_ICON_LOOKUP_DIR_RTL) + dir_suffix = "-rtl"; + else + dir_suffix = NULL; + + for (i = 0; icon_names[i]; i++) + { + if (icon_name_is_symbolic (icon_names[i])) + has_symbolic = TRUE; + else + has_regular = TRUE; + } + + if ((flags & GTK_ICON_LOOKUP_FORCE_REGULAR) && has_symbolic) + { + new_names = g_ptr_array_new_with_free_func (g_free); + for (i = 0; icon_names[i]; i++) + { + if (icon_name_is_symbolic (icon_names[i])) + icon_name_list_add_icon (new_names, dir_suffix, g_strndup (icon_names[i], strlen (icon_names[i]) - strlen ("-symbolic"))); + else + icon_name_list_add_icon (new_names, dir_suffix, g_strdup (icon_names[i])); + } + for (i = 0; icon_names[i]; i++) + { + if (icon_name_is_symbolic (icon_names[i])) + icon_name_list_add_icon (new_names, dir_suffix, g_strdup (icon_names[i])); + } + g_ptr_array_add (new_names, NULL); + + icon_info = real_choose_icon (icon_theme, + (const gchar **) new_names->pdata, + size, + scale, + flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC)); + + g_ptr_array_free (new_names, TRUE); + } + else if ((flags & GTK_ICON_LOOKUP_FORCE_SYMBOLIC) && has_regular) + { + new_names = g_ptr_array_new_with_free_func (g_free); + for (i = 0; icon_names[i]; i++) + { + if (!icon_name_is_symbolic (icon_names[i])) + icon_name_list_add_icon (new_names, dir_suffix, g_strconcat (icon_names[i], "-symbolic", NULL)); + else + icon_name_list_add_icon (new_names, dir_suffix, g_strdup (icon_names[i])); + } + for (i = 0; icon_names[i]; i++) + { + if (!icon_name_is_symbolic (icon_names[i])) + icon_name_list_add_icon (new_names, dir_suffix, g_strdup (icon_names[i])); + } + g_ptr_array_add (new_names, NULL); + + icon_info = real_choose_icon (icon_theme, + (const gchar **) new_names->pdata, + size, + scale, + flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC)); + + g_ptr_array_free (new_names, TRUE); + } + else if (dir_suffix) + { + new_names = g_ptr_array_new_with_free_func (g_free); + for (i = 0; icon_names[i]; i++) + { + icon_name_list_add_icon (new_names, dir_suffix, g_strdup (icon_names[i])); + } + g_ptr_array_add (new_names, NULL); + + icon_info = real_choose_icon (icon_theme, + (const gchar **) new_names->pdata, + size, + scale, + flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC)); + + g_ptr_array_free (new_names, TRUE); + } + else + { + icon_info = real_choose_icon (icon_theme, + icon_names, + size, + scale, + flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC)); + } + + return icon_info; +} + +/** + * gtk_icon_theme_lookup_icon: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of the icon to lookup + * @size: desired icon size + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up a named icon and returns a #GtkIconInfo containing + * information such as the filename of the icon. The icon + * can then be rendered into a pixbuf using + * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() + * combines these two steps if all you need is the pixbuf.) + * + * When rendering on displays with high pixel densities you should not + * use a @size multiplied by the scaling factor returned by functions + * like gdk_window_get_scale_factor(). Instead, you should use + * gtk_icon_theme_lookup_icon_for_scale(), as the assets loaded + * for a given scaling factor may be different. + * + * Returns: (nullable) (transfer full): a #GtkIconInfo object + * containing information about the icon, or %NULL if the + * icon wasn’t found. + * + * Since: 2.4 + */ +GtkIconInfo * +gtk_icon_theme_lookup_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags) +{ + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + + GTK_NOTE (ICONTHEME, g_message ("looking up icon %s", icon_name)); + + return gtk_icon_theme_lookup_icon_for_scale (icon_theme, icon_name, + size, 1, flags); +} + +/** + * gtk_icon_theme_lookup_icon_for_scale: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of the icon to lookup + * @size: desired icon size + * @scale: the desired scale + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up a named icon for a particular window scale and returns a + * #GtkIconInfo containing information such as the filename of the + * icon. The icon can then be rendered into a pixbuf using + * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() combines + * these two steps if all you need is the pixbuf.) + * + * Returns: (nullable) (transfer full): a #GtkIconInfo object + * containing information about the icon, or %NULL if the + * icon wasn’t found. + * + * Since: 3.10 + */ +GtkIconInfo * +gtk_icon_theme_lookup_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GtkIconLookupFlags flags) +{ + GtkIconInfo *info; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_return_val_if_fail (scale >= 1, NULL); + + GTK_NOTE (ICONTHEME, g_message ("looking up icon %s for scale %d", icon_name, scale)); + + if (flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) + { + gchar **names, **nonsymbolic_names; + gint dashes, i; + gchar *p, *nonsymbolic_icon_name; + gboolean is_symbolic; + + is_symbolic = icon_name_is_symbolic (icon_name); + if (is_symbolic) + nonsymbolic_icon_name = g_strndup (icon_name, strlen (icon_name) - strlen ("-symbolic")); + else + nonsymbolic_icon_name = g_strdup (icon_name); + + dashes = 0; + for (p = (gchar *) nonsymbolic_icon_name; *p; p++) + if (*p == '-') + dashes++; + + nonsymbolic_names = g_new (gchar *, dashes + 2); + nonsymbolic_names[0] = nonsymbolic_icon_name; + + for (i = 1; i <= dashes; i++) + { + nonsymbolic_names[i] = g_strdup (nonsymbolic_names[i - 1]); + p = strrchr (nonsymbolic_names[i], '-'); + *p = '\0'; + } + nonsymbolic_names[dashes + 1] = NULL; + + if (is_symbolic) + { + names = g_new (gchar *, 2 * dashes + 3); + for (i = 0; nonsymbolic_names[i] != NULL; i++) + { + names[i] = g_strconcat (nonsymbolic_names[i], "-symbolic", NULL); + names[dashes + 1 + i] = nonsymbolic_names[i]; + } + + names[dashes + 1 + i] = NULL; + g_free (nonsymbolic_names); + } + else + { + names = nonsymbolic_names; + } + + info = choose_icon (icon_theme, (const gchar **) names, size, scale, flags); + + g_strfreev (names); + } + else + { + const gchar *names[2]; + + names[0] = icon_name; + names[1] = NULL; + + info = choose_icon (icon_theme, names, size, scale, flags); + } + + return info; +} + +/** + * gtk_icon_theme_choose_icon: + * @icon_theme: a #GtkIconTheme + * @icon_names: (array zero-terminated=1): %NULL-terminated array of + * icon names to lookup + * @size: desired icon size + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up a named icon and returns a #GtkIconInfo containing + * information such as the filename of the icon. The icon + * can then be rendered into a pixbuf using + * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() + * combines these two steps if all you need is the pixbuf.) + * + * If @icon_names contains more than one name, this function + * tries them all in the given order before falling back to + * inherited icon themes. + * + * Returns: (nullable) (transfer full): a #GtkIconInfo object + * containing information about the icon, or %NULL if the icon wasn’t + * found. + * + * Since: 2.12 + */ +GtkIconInfo * +gtk_icon_theme_choose_icon (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + GtkIconLookupFlags flags) +{ + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_names != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_warn_if_fail ((flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) == 0); + + return choose_icon (icon_theme, icon_names, size, 1, flags); +} + +/** + * gtk_icon_theme_choose_icon_for_scale: + * @icon_theme: a #GtkIconTheme + * @icon_names: (array zero-terminated=1): %NULL-terminated + * array of icon names to lookup + * @size: desired icon size + * @scale: desired scale + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up a named icon for a particular window scale and returns + * a #GtkIconInfo containing information such as the filename of the + * icon. The icon can then be rendered into a pixbuf using + * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() + * combines these two steps if all you need is the pixbuf.) + * + * If @icon_names contains more than one name, this function + * tries them all in the given order before falling back to + * inherited icon themes. + * + * Returns: (nullable) (transfer full): a #GtkIconInfo object + * containing information about the icon, or %NULL if the + * icon wasn’t found. + * + * Since: 3.10 + */ +GtkIconInfo * +gtk_icon_theme_choose_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + gint scale, + GtkIconLookupFlags flags) +{ + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_names != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_return_val_if_fail (scale >= 1, NULL); + g_warn_if_fail ((flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) == 0); + + return choose_icon (icon_theme, icon_names, size, scale, flags); +} + + +/* Error quark */ +GQuark +gtk_icon_theme_error_quark (void) +{ + return g_quark_from_static_string ("gtk-icon-theme-error-quark"); +} + +/** + * gtk_icon_theme_load_icon: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of the icon to lookup + * @size: the desired icon size. The resulting icon may not be + * exactly this size; see gtk_icon_info_load_icon(). + * @flags: flags modifying the behavior of the icon lookup + * @error: (allow-none): Location to store error information on failure, + * or %NULL. + * + * Looks up an icon in an icon theme, scales it to the given size + * and renders it into a pixbuf. This is a convenience function; + * if more details about the icon are needed, use + * gtk_icon_theme_lookup_icon() followed by gtk_icon_info_load_icon(). + * + * Note that you probably want to listen for icon theme changes and + * update the icon. This is usually done by connecting to the + * GtkWidget::style-set signal. If for some reason you do not want to + * update the icon when the icon theme changes, you should consider + * using gdk_pixbuf_copy() to make a private copy of the pixbuf + * returned by this function. Otherwise GTK+ may need to keep the old + * icon theme loaded, which would be a waste of memory. + * + * Returns: (nullable) (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. %NULL if the icon isn’t found. + * + * Since: 2.4 + */ +GdkPixbuf * +gtk_icon_theme_load_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags, + GError **error) +{ + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return gtk_icon_theme_load_icon_for_scale (icon_theme, icon_name, + size, 1, flags, error); +} + +/** + * gtk_icon_theme_load_icon_for_scale: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of the icon to lookup + * @size: the desired icon size. The resulting icon may not be + * exactly this size; see gtk_icon_info_load_icon(). + * @scale: desired scale + * @flags: flags modifying the behavior of the icon lookup + * @error: (allow-none): Location to store error information on failure, + * or %NULL. + * + * Looks up an icon in an icon theme for a particular window scale, + * scales it to the given size and renders it into a pixbuf. This is a + * convenience function; if more details about the icon are needed, + * use gtk_icon_theme_lookup_icon() followed by + * gtk_icon_info_load_icon(). + * + * Note that you probably want to listen for icon theme changes and + * update the icon. This is usually done by connecting to the + * GtkWidget::style-set signal. If for some reason you do not want to + * update the icon when the icon theme changes, you should consider + * using gdk_pixbuf_copy() to make a private copy of the pixbuf + * returned by this function. Otherwise GTK+ may need to keep the old + * icon theme loaded, which would be a waste of memory. + * + * Returns: (nullable) (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. %NULL if the icon isn’t found. + * + * Since: 3.10 + */ +GdkPixbuf * +gtk_icon_theme_load_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GtkIconLookupFlags flags, + GError **error) +{ + GtkIconInfo *icon_info; + GdkPixbuf *pixbuf = NULL; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (scale >= 1, NULL); + + icon_info = gtk_icon_theme_lookup_icon_for_scale (icon_theme, icon_name, size, scale, + flags | GTK_ICON_LOOKUP_USE_BUILTIN); + if (!icon_info) + { + g_set_error (error, GTK_ICON_THEME_ERROR, GTK_ICON_THEME_NOT_FOUND, + _("Icon '%s' not present in theme %s"), icon_name, icon_theme->priv->current_theme); + return NULL; + } + + pixbuf = gtk_icon_info_load_icon (icon_info, error); + g_prefix_error (error, "Failed to load %s: ", icon_info->filename); + g_object_unref (icon_info); + + return pixbuf; +} + +/** + * gtk_icon_theme_load_surface: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of the icon to lookup + * @size: the desired icon size. The resulting icon may not be + * exactly this size; see gtk_icon_info_load_icon(). + * @scale: desired scale + * @for_window: (allow-none): #GdkWindow to optimize drawing for, or %NULL + * @flags: flags modifying the behavior of the icon lookup + * @error: (allow-none): Location to store error information on failure, + * or %NULL. + * + * Looks up an icon in an icon theme for a particular window scale, + * scales it to the given size and renders it into a cairo surface. This is a + * convenience function; if more details about the icon are needed, + * use gtk_icon_theme_lookup_icon() followed by + * gtk_icon_info_load_surface(). + * + * Note that you probably want to listen for icon theme changes and + * update the icon. This is usually done by connecting to the + * GtkWidget::style-set signal. + * + * Returns: (nullable) (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 cairo_surface_destroy() to + * release your reference to the icon. %NULL if the icon isn’t + * found. + * + * Since: 3.10 + */ +cairo_surface_t * +gtk_icon_theme_load_surface (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GdkWindow *for_window, + GtkIconLookupFlags flags, + GError **error) +{ + GtkIconInfo *icon_info; + cairo_surface_t *surface = NULL; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || + (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (scale >= 1, NULL); + + icon_info = gtk_icon_theme_lookup_icon_for_scale (icon_theme, icon_name, size, scale, + flags | GTK_ICON_LOOKUP_USE_BUILTIN); + if (!icon_info) + { + g_set_error (error, GTK_ICON_THEME_ERROR, GTK_ICON_THEME_NOT_FOUND, + _("Icon '%s' not present in theme %s"), icon_name, icon_theme->priv->current_theme); + return NULL; + } + + surface = gtk_icon_info_load_surface (icon_info, for_window, error); + g_object_unref (icon_info); + + return surface; +} + +/** + * gtk_icon_theme_has_icon: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of an icon + * + * Checks whether an icon theme includes an icon + * for a particular name. + * + * Returns: %TRUE if @icon_theme includes an + * icon for @icon_name. + * + * Since: 2.4 + */ +gboolean +gtk_icon_theme_has_icon (GtkIconTheme *icon_theme, + const gchar *icon_name) +{ + GtkIconThemePrivate *priv; + GList *l; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), FALSE); + g_return_val_if_fail (icon_name != NULL, FALSE); + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + for (l = priv->dir_mtimes; l; l = l->next) + { + IconThemeDirMtime *dir_mtime = l->data; + GtkIconCache *cache = dir_mtime->cache; + + if (cache && _gtk_icon_cache_has_icon (cache, icon_name)) + return TRUE; + } + + for (l = priv->themes; l; l = l->next) + { + if (theme_has_icon (l->data, icon_name)) + return TRUE; + } + + if (icon_theme_builtin_icons && + g_hash_table_lookup_extended (icon_theme_builtin_icons, + icon_name, NULL, NULL)) + return TRUE; + + return FALSE; +} + +static void +add_size (gpointer key, + gpointer value, + gpointer user_data) +{ + gint **res_p = user_data; + + **res_p = GPOINTER_TO_INT (key); + + (*res_p)++; +} + +/** + * gtk_icon_theme_get_icon_sizes: + * @icon_theme: a #GtkIconTheme + * @icon_name: the name of an icon + * + * Returns an array of integers describing the sizes at which + * the icon is available without scaling. A size of -1 means + * that the icon is available in a scalable format. The array + * is zero-terminated. + * + * Returns: (array zero-terminated=1) (transfer full): An newly + * allocated array describing the sizes at which the icon is + * available. The array should be freed with g_free() when it is no + * longer needed. + * + * Since: 2.6 + */ +gint * +gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme, + const gchar *icon_name) +{ + GList *l, *d, *icons; + GHashTable *sizes; + gint *result, *r; + guint suffix; + GtkIconThemePrivate *priv; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + sizes = g_hash_table_new (g_direct_hash, g_direct_equal); + + for (l = priv->themes; l; l = l->next) + { + IconTheme *theme = l->data; + for (d = theme->dirs; d; d = d->next) + { + IconThemeDir *dir = d->data; + + if (dir->type != ICON_THEME_DIR_SCALABLE && g_hash_table_lookup_extended (sizes, GINT_TO_POINTER (dir->size), NULL, NULL)) + continue; + + suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL); + if (suffix != ICON_SUFFIX_NONE) + { + if (suffix == ICON_SUFFIX_SVG) + g_hash_table_insert (sizes, GINT_TO_POINTER (-1), NULL); + else + g_hash_table_insert (sizes, GINT_TO_POINTER (dir->size), NULL); + } + } + } + + if (icon_theme_builtin_icons) + { + icons = g_hash_table_lookup (icon_theme_builtin_icons, icon_name); + + while (icons) + { + BuiltinIcon *icon = icons->data; + + g_hash_table_insert (sizes, GINT_TO_POINTER (icon->size), NULL); + icons = icons->next; + } + } + + r = result = g_new0 (gint, g_hash_table_size (sizes) + 1); + + g_hash_table_foreach (sizes, add_size, &r); + g_hash_table_destroy (sizes); + + return result; +} + +static void +add_key_to_hash (gpointer key, + gpointer value, + gpointer user_data) +{ + GHashTable *hash = user_data; + + g_hash_table_insert (hash, key, NULL); +} + +static void +add_key_to_list (gpointer key, + gpointer value, + gpointer user_data) +{ + GList **list = user_data; + + *list = g_list_prepend (*list, g_strdup (key)); +} + +/** + * gtk_icon_theme_list_icons: + * @icon_theme: a #GtkIconTheme + * @context: (allow-none): a string identifying a particular type of + * icon, or %NULL to list all icons. + * + * Lists the icons in the current icon theme. Only a subset + * of the icons can be listed by providing a context string. + * The set of values for the context string is system dependent, + * but will typically include such values as “Applications” and + * “MimeTypes”. Contexts are explained in the + * [Icon Theme Specification](http://www.freedesktop.org/wiki/Specifications/icon-theme-spec). + * The standard contexts are listed in the + * [Icon Naming Specification](http://www.freedesktop.org/wiki/Specifications/icon-naming-spec). + * Also see gtk_icon_theme_list_contexts(). + * + * Returns: (element-type utf8) (transfer full): a #GList list + * holding the names of all the icons in the theme. You must + * first free each element in the list with g_free(), then + * free the list itself with g_list_free(). + * + * Since: 2.4 + */ +GList * +gtk_icon_theme_list_icons (GtkIconTheme *icon_theme, + const gchar *context) +{ + GtkIconThemePrivate *priv; + GHashTable *icons; + GList *list, *l; + GQuark context_quark; + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + if (context) + { + context_quark = g_quark_try_string (context); + + if (!context_quark) + return NULL; + } + else + context_quark = 0; + + icons = g_hash_table_new (g_str_hash, g_str_equal); + + l = priv->themes; + while (l != NULL) + { + theme_list_icons (l->data, icons, context_quark); + l = l->next; + } + + if (context_quark == 0) + g_hash_table_foreach (priv->unthemed_icons, + add_key_to_hash, + icons); + + list = NULL; + + g_hash_table_foreach (icons, + add_key_to_list, + &list); + + g_hash_table_destroy (icons); + + return list; +} + +/** + * gtk_icon_theme_list_contexts: + * @icon_theme: a #GtkIconTheme + * + * Gets the list of contexts available within the current + * hierarchy of icon themes. + * See gtk_icon_theme_list_icons() for details about contexts. + * + * Returns: (element-type utf8) (transfer full): a #GList list + * holding the names of all the contexts in the theme. You must first + * free each element in the list with g_free(), then free the list + * itself with g_list_free(). + * + * Since: 2.12 + */ +GList * +gtk_icon_theme_list_contexts (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + GHashTable *contexts; + GList *list, *l; + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + contexts = g_hash_table_new (g_str_hash, g_str_equal); + + l = priv->themes; + while (l != NULL) + { + theme_list_contexts (l->data, contexts); + l = l->next; + } + + list = NULL; + + g_hash_table_foreach (contexts, + add_key_to_list, + &list); + + g_hash_table_destroy (contexts); + + return list; +} + +/** + * gtk_icon_theme_get_example_icon_name: + * @icon_theme: a #GtkIconTheme + * + * Gets the name of an icon that is representative of the + * current theme (for instance, to use when presenting + * a list of themes to the user.) + * + * Returns: (nullable): the name of an example icon or %NULL. + * Free with g_free(). + * + * Since: 2.4 + */ +gchar * +gtk_icon_theme_get_example_icon_name (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + GList *l; + IconTheme *theme; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + l = priv->themes; + while (l != NULL) + { + theme = l->data; + if (theme->example) + return g_strdup (theme->example); + + l = l->next; + } + + return NULL; +} + + +static gboolean +rescan_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + IconThemeDirMtime *dir_mtime; + GList *d; + gint stat_res; + GStatBuf stat_buf; + GTimeVal tv; + + priv = icon_theme->priv; + + for (d = priv->dir_mtimes; d != NULL; d = d->next) + { + dir_mtime = d->data; + + stat_res = g_stat (dir_mtime->dir, &stat_buf); + + /* dir mtime didn't change */ + if (stat_res == 0 && dir_mtime->exists && + S_ISDIR (stat_buf.st_mode) && + dir_mtime->mtime == stat_buf.st_mtime) + continue; + /* didn't exist before, and still doesn't */ + if (!dir_mtime->exists && + (stat_res != 0 || !S_ISDIR (stat_buf.st_mode))) + continue; + + return TRUE; + } + + g_get_current_time (&tv); + priv->last_stat_time = tv.tv_sec; + + return FALSE; +} + +/** + * gtk_icon_theme_rescan_if_needed: + * @icon_theme: a #GtkIconTheme + * + * Checks to see if the icon theme has changed; if it has, any + * currently cached information is discarded and will be reloaded + * next time @icon_theme is accessed. + * + * Returns: %TRUE if the icon theme has changed and needed + * to be reloaded. + * + * Since: 2.4 + */ +gboolean +gtk_icon_theme_rescan_if_needed (GtkIconTheme *icon_theme) +{ + gboolean retval; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), FALSE); + + retval = rescan_themes (icon_theme); + if (retval) + do_theme_change (icon_theme); + + return retval; +} + +static void +theme_destroy (IconTheme *theme) +{ + g_free (theme->display_name); + g_free (theme->comment); + g_free (theme->name); + g_free (theme->example); + + g_list_free_full (theme->dirs, (GDestroyNotify) theme_dir_destroy); + + g_free (theme); +} + +static void +theme_dir_destroy (IconThemeDir *dir) +{ + if (dir->cache) + _gtk_icon_cache_unref (dir->cache); + if (dir->icons) + g_hash_table_destroy (dir->icons); + + g_free (dir->dir); + g_free (dir->subdir); + g_free (dir); +} + +static int +theme_dir_size_difference (IconThemeDir *dir, + gint size, + gint scale) +{ + gint scaled_size, scaled_dir_size; + gint min, max; + + scaled_size = size * scale; + scaled_dir_size = dir->size * dir->scale; + + switch (dir->type) + { + case ICON_THEME_DIR_FIXED: + return abs (scaled_size - scaled_dir_size); + break; + case ICON_THEME_DIR_SCALABLE: + if (scaled_size < (dir->min_size * dir->scale)) + return (dir->min_size * dir->scale) - scaled_size; + if (size > (dir->max_size * dir->scale)) + return scaled_size - (dir->max_size * dir->scale); + return 0; + break; + case ICON_THEME_DIR_THRESHOLD: + min = (dir->size - dir->threshold) * dir->scale; + max = (dir->size + dir->threshold) * dir->scale; + if (scaled_size < min) + return min - scaled_size; + if (scaled_size > max) + return scaled_size - max; + return 0; + break; + case ICON_THEME_DIR_UNTHEMED: + g_assert_not_reached (); + break; + } + g_assert_not_reached (); + return 1000; +} + +static const gchar * +string_from_suffix (IconSuffix suffix) +{ + switch (suffix) + { + case ICON_SUFFIX_XPM: + return ".xpm"; + case ICON_SUFFIX_SVG: + return ".svg"; + case ICON_SUFFIX_PNG: + return ".png"; + case ICON_SUFFIX_SYMBOLIC_PNG: + return ".symbolic.png"; + default: + g_assert_not_reached(); + } + return NULL; +} + +static IconSuffix +suffix_from_name (const gchar *name) +{ + IconSuffix retval = ICON_SUFFIX_NONE; + + if (name != NULL) + { + if (g_str_has_suffix (name, ".symbolic.png")) + retval = ICON_SUFFIX_SYMBOLIC_PNG; + else if (g_str_has_suffix (name, ".png")) + retval = ICON_SUFFIX_PNG; + else if (g_str_has_suffix (name, ".svg")) + retval = ICON_SUFFIX_SVG; + else if (g_str_has_suffix (name, ".xpm")) + retval = ICON_SUFFIX_XPM; + } + + return retval; +} + +static IconSuffix +best_suffix (IconSuffix suffix, + gboolean allow_svg) +{ + if ((suffix & ICON_SUFFIX_SYMBOLIC_PNG) != 0) + return ICON_SUFFIX_SYMBOLIC_PNG; + else if ((suffix & ICON_SUFFIX_PNG) != 0) + return ICON_SUFFIX_PNG; + else if (allow_svg && ((suffix & ICON_SUFFIX_SVG) != 0)) + return ICON_SUFFIX_SVG; + else if ((suffix & ICON_SUFFIX_XPM) != 0) + return ICON_SUFFIX_XPM; + else + return ICON_SUFFIX_NONE; +} + +static IconSuffix +theme_dir_get_icon_suffix (IconThemeDir *dir, + const gchar *icon_name, + gboolean *has_icon_file) +{ + IconSuffix suffix, symbolic_suffix; + + if (dir->cache) + { + suffix = (IconSuffix)_gtk_icon_cache_get_icon_flags (dir->cache, + icon_name, + dir->subdir_index); + + if (icon_name_is_symbolic (icon_name)) + { + /* Look for foo-symbolic.symbolic.png, as the cache only stores the ".png" suffix */ + char *icon_name_with_prefix = g_strconcat (icon_name, ".symbolic", NULL); + symbolic_suffix = (IconSuffix)_gtk_icon_cache_get_icon_flags (dir->cache, + icon_name_with_prefix, + dir->subdir_index); + g_free (icon_name_with_prefix); + + if (symbolic_suffix & ICON_SUFFIX_PNG) + suffix = ICON_SUFFIX_SYMBOLIC_PNG; + } + + if (has_icon_file) + *has_icon_file = suffix & HAS_ICON_FILE; + + suffix = suffix & ~HAS_ICON_FILE; + } + else + suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name)); + + GTK_NOTE (ICONTHEME, g_message ("get icon suffix%s: %u", dir->cache ? " (cached)" : "", suffix)); + + return suffix; +} + +/* returns TRUE if dir_a is a better match */ +static gboolean +compare_dir_matches (IconThemeDir *dir_a, gint difference_a, + IconThemeDir *dir_b, gint difference_b, + gint requested_size, + gint requested_scale) +{ + gint diff_a; + gint diff_b; + + if (difference_a == 0) + { + if (difference_b != 0) + return TRUE; + + /* a and b both exact matches */ + } + else + { + /* If scaling, *always* prefer downscaling */ + if (dir_a->size >= requested_size && + dir_b->size < requested_size) + return TRUE; + + if (dir_a->size < requested_size && + dir_b->size >= requested_size) + return FALSE; + + /* Otherwise prefer the closest match */ + + if (difference_a < difference_b) + return TRUE; + + if (difference_a > difference_b) + return FALSE; + + /* same pixel difference */ + } + + if (dir_a->scale == requested_scale && + dir_b->scale != requested_scale) + return TRUE; + + if (dir_a->scale != requested_scale && + dir_b->scale == requested_scale) + return FALSE; + + /* a and b both match the scale */ + + if (dir_a->type != ICON_THEME_DIR_SCALABLE && + dir_b->type == ICON_THEME_DIR_SCALABLE) + return TRUE; + + if (dir_a->type == ICON_THEME_DIR_SCALABLE && + dir_b->type != ICON_THEME_DIR_SCALABLE) + return FALSE; + + /* a and b both are scalable */ + + diff_a = abs (requested_size * requested_scale - dir_a->size * dir_a->scale); + diff_b = abs (requested_size * requested_scale - dir_b->size * dir_b->scale); + + return diff_a <= diff_b; +} + +static GtkIconInfo * +theme_lookup_icon (IconTheme *theme, + const gchar *icon_name, + gint size, + gint scale, + gboolean allow_svg, + gboolean use_builtin) +{ + GList *dirs, *l; + IconThemeDir *dir, *min_dir; + gchar *file; + gint min_difference, difference; + BuiltinIcon *closest_builtin = NULL; + IconSuffix suffix; + + min_difference = G_MAXINT; + min_dir = NULL; + + /* Builtin icons are logically part of the default theme and + * are searched before other subdirectories of the default theme. + */ + if (use_builtin && strcmp (theme->name, FALLBACK_ICON_THEME) == 0) + { + closest_builtin = find_builtin_icon (icon_name, + size, scale, + &min_difference); + + if (min_difference == 0) + return icon_info_new_builtin (closest_builtin); + } + + dirs = theme->dirs; + + l = dirs; + while (l != NULL) + { + dir = l->data; + + GTK_NOTE (ICONTHEME, g_message ("look up icon dir %s", dir->dir)); + suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL); + if (best_suffix (suffix, allow_svg) != ICON_SUFFIX_NONE) + { + difference = theme_dir_size_difference (dir, size, scale); + if (min_dir == NULL || + compare_dir_matches (dir, difference, + min_dir, min_difference, + size, scale)) + { + min_dir = dir; + min_difference = difference; + } + } + + l = l->next; + } + + if (min_dir) + { + GtkIconInfo *icon_info; + gboolean has_icon_file = FALSE; + + icon_info = icon_info_new (min_dir->type, min_dir->size, min_dir->scale); + icon_info->min_size = min_dir->min_size; + icon_info->max_size = min_dir->max_size; + + suffix = theme_dir_get_icon_suffix (min_dir, icon_name, &has_icon_file); + suffix = best_suffix (suffix, allow_svg); + g_assert (suffix != ICON_SUFFIX_NONE); + + if (min_dir->dir) + { + file = g_strconcat (icon_name, string_from_suffix (suffix), NULL); + icon_info->filename = g_build_filename (min_dir->dir, file, NULL); + + if (min_dir->is_resource) + { + gchar *uri; + uri = g_strconcat ("resource://", icon_info->filename, NULL); + icon_info->icon_file = g_file_new_for_uri (uri); + g_free (uri); + } + else + icon_info->icon_file = g_file_new_for_path (icon_info->filename); + + icon_info->is_svg = suffix == ICON_SUFFIX_SVG; + icon_info->is_resource = min_dir->is_resource; + g_free (file); + } + else + { + icon_info->filename = NULL; + icon_info->icon_file = NULL; + } + + if (min_dir->cache) + { + icon_info->cache_pixbuf = _gtk_icon_cache_get_icon (min_dir->cache, icon_name, + min_dir->subdir_index); + } + + return icon_info; + } + + if (closest_builtin) + return icon_info_new_builtin (closest_builtin); + + return NULL; +} + +static void +theme_list_icons (IconTheme *theme, + GHashTable *icons, + GQuark context) +{ + GList *l = theme->dirs; + IconThemeDir *dir; + + while (l != NULL) + { + dir = l->data; + + if (context == dir->context || + context == 0) + { + if (dir->cache) + _gtk_icon_cache_add_icons (dir->cache, dir->subdir, icons); + else + g_hash_table_foreach (dir->icons, add_key_to_hash, icons); + } + l = l->next; + } +} + +static gboolean +theme_has_icon (IconTheme *theme, + const gchar *icon_name) +{ + GList *l; + + for (l = theme->dirs; l; l = l->next) + { + IconThemeDir *dir = l->data; + + if (dir->cache) + { + if (_gtk_icon_cache_has_icon (dir->cache, icon_name)) + return TRUE; + } + else + { + if (g_hash_table_lookup (dir->icons, icon_name) != NULL) + return TRUE; + } + } + + return FALSE; +} + +static void +theme_list_contexts (IconTheme *theme, + GHashTable *contexts) +{ + GList *l = theme->dirs; + IconThemeDir *dir; + const gchar *context; + + while (l != NULL) + { + dir = l->data; + + /* The "Context" key can be unset */ + if (dir->context != 0) + { + context = g_quark_to_string (dir->context); + g_hash_table_replace (contexts, (gpointer) context, NULL); + } + + l = l->next; + } +} + +static gboolean +scan_directory (GtkIconThemePrivate *icon_theme, + IconThemeDir *dir, + gchar *full_dir) +{ + GDir *gdir; + const gchar *name; + + GTK_NOTE (ICONTHEME, g_message ("scanning directory %s", full_dir)); + + gdir = g_dir_open (full_dir, 0, NULL); + + if (gdir == NULL) + return FALSE; + + dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + while ((name = g_dir_read_name (gdir))) + { + gchar *base_name; + IconSuffix suffix, hash_suffix; + + suffix = suffix_from_name (name); + if (suffix == ICON_SUFFIX_NONE) + continue; + + base_name = strip_suffix (name); + + hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (dir->icons, base_name)); + /* takes ownership of base_name */ + g_hash_table_replace (dir->icons, base_name, GUINT_TO_POINTER (hash_suffix|suffix)); + } + + g_dir_close (gdir); + + return g_hash_table_size (dir->icons) > 0; +} + +static gboolean +scan_resources (GtkIconThemePrivate *icon_theme, + IconThemeDir *dir, + gchar *full_dir) +{ + gint i; + gchar **children; + + GTK_NOTE (ICONTHEME, g_message ("scanning resources %s", full_dir)); + + children = g_resources_enumerate_children (full_dir, 0, NULL); + if (!children) + return FALSE; + + dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + for (i = 0; children[i]; i++) + { + gchar *base_name; + IconSuffix suffix, hash_suffix; + + suffix = suffix_from_name (children[i]); + if (suffix == ICON_SUFFIX_NONE) + continue; + + base_name = strip_suffix (children[i]); + + hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (dir->icons, base_name)); + /* takes ownership of base_name */ + g_hash_table_replace (dir->icons, base_name, GUINT_TO_POINTER (hash_suffix|suffix)); + } + g_strfreev (children); + + return g_hash_table_size (dir->icons) > 0; +} + +static void +theme_subdir_load (GtkIconTheme *icon_theme, + IconTheme *theme, + GKeyFile *theme_file, + gchar *subdir) +{ + GList *d; + gchar *type_string; + IconThemeDir *dir; + IconThemeDirType type; + gchar *context_string; + GQuark context; + gint size; + gint min_size; + gint max_size; + gint threshold; + gchar *full_dir; + GError *error = NULL; + IconThemeDirMtime *dir_mtime; + gint scale; + gboolean has_icons; + + size = g_key_file_get_integer (theme_file, subdir, "Size", &error); + if (error) + { + g_error_free (error); + g_warning ("Theme directory %s of theme %s has no size field\n", + subdir, theme->name); + return; + } + + type = ICON_THEME_DIR_THRESHOLD; + type_string = g_key_file_get_string (theme_file, subdir, "Type", NULL); + if (type_string) + { + if (strcmp (type_string, "Fixed") == 0) + type = ICON_THEME_DIR_FIXED; + else if (strcmp (type_string, "Scalable") == 0) + type = ICON_THEME_DIR_SCALABLE; + else if (strcmp (type_string, "Threshold") == 0) + type = ICON_THEME_DIR_THRESHOLD; + + g_free (type_string); + } + + context = 0; + context_string = g_key_file_get_string (theme_file, subdir, "Context", NULL); + if (context_string) + { + context = g_quark_from_string (context_string); + g_free (context_string); + } + + 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; + + 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; + + 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; + + if (g_key_file_has_key (theme_file, subdir, "Scale", NULL)) + scale = g_key_file_get_integer (theme_file, subdir, "Scale", NULL); + else + scale = 1; + + for (d = icon_theme->priv->dir_mtimes; d; d = d->next) + { + dir_mtime = (IconThemeDirMtime *)d->data; + + if (!dir_mtime->exists) + continue; /* directory doesn't exist */ + + full_dir = g_build_filename (dir_mtime->dir, subdir, NULL); + + /* First, see if we have a cache for the directory */ + if (dir_mtime->cache != NULL || g_file_test (full_dir, G_FILE_TEST_IS_DIR)) + { + if (dir_mtime->cache == NULL) + { + /* This will return NULL if the cache doesn't exist or is outdated */ + dir_mtime->cache = _gtk_icon_cache_new_for_path (dir_mtime->dir); + } + + dir = g_new0 (IconThemeDir, 1); + dir->type = type; + dir->is_resource = FALSE; + dir->context = context; + dir->size = size; + dir->min_size = min_size; + dir->max_size = max_size; + dir->threshold = threshold; + dir->dir = full_dir; + dir->subdir = g_strdup (subdir); + dir->scale = scale; + + if (dir_mtime->cache != NULL) + { + dir->cache = _gtk_icon_cache_ref (dir_mtime->cache); + dir->subdir_index = _gtk_icon_cache_get_directory_index (dir->cache, dir->subdir); + has_icons = _gtk_icon_cache_has_icons (dir->cache, dir->subdir); + } + else + { + dir->cache = NULL; + dir->subdir_index = -1; + has_icons = scan_directory (icon_theme->priv, dir, full_dir); + } + + if (has_icons) + theme->dirs = g_list_prepend (theme->dirs, dir); + else + theme_dir_destroy (dir); + } + else + g_free (full_dir); + } + + if (strcmp (theme->name, FALLBACK_ICON_THEME) == 0) + { + for (d = icon_theme->priv->resource_paths; d; d = d->next) + { + /* Force a trailing / here, to avoid extra copies in GResource */ + full_dir = g_build_filename ((const gchar *)d->data, subdir, " ", NULL); + full_dir[strlen (full_dir) - 1] = '\0'; + dir = g_new0 (IconThemeDir, 1); + dir->type = type; + dir->is_resource = TRUE; + dir->context = context; + dir->size = size; + dir->min_size = min_size; + dir->max_size = max_size; + dir->threshold = threshold; + dir->dir = full_dir; + dir->subdir = g_strdup (subdir); + dir->scale = scale; + dir->cache = NULL; + dir->subdir_index = -1; + + if (scan_resources (icon_theme->priv, dir, full_dir)) + theme->dirs = g_list_prepend (theme->dirs, dir); + else + theme_dir_destroy (dir); + } + } +} + +/* + * GtkIconInfo + */ + +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 (IconThemeDirType type, + gint dir_size, + gint dir_scale) +{ + GtkIconInfo *icon_info; + + icon_info = g_object_new (GTK_TYPE_ICON_INFO, NULL); + + icon_info->dir_type = type; + icon_info->dir_size = dir_size; + icon_info->dir_scale = dir_scale; + icon_info->unscaled_scale = 1.0; + icon_info->is_svg = FALSE; + icon_info->is_resource = FALSE; + + return icon_info; +} + +/* 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; + + dup = icon_info_new (icon_info->dir_type, icon_info->dir_size, icon_info->dir_scale); + + dup->filename = g_strdup (icon_info->filename); + dup->is_svg = icon_info->is_svg; + + 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->scale = icon_info->scale; + dup->unscaled_scale = icon_info->unscaled_scale; + dup->desired_size = icon_info->desired_size; + dup->desired_scale = icon_info->desired_scale; + dup->forced_size = icon_info->forced_size; + dup->emblems_applied = icon_info->emblems_applied; + dup->is_resource = icon_info->is_resource; + dup->min_size = icon_info->min_size; + dup->max_size = icon_info->max_size; + dup->symbolic_width = icon_info->symbolic_width; + dup->symbolic_height = icon_info->symbolic_height; + + return dup; +} + +static GtkIconInfo * +icon_info_new_builtin (BuiltinIcon *icon) +{ + GtkIconInfo *icon_info = icon_info_new (ICON_THEME_DIR_THRESHOLD, icon->size, 1); + + icon_info->cache_pixbuf = g_object_ref (icon->pixbuf); + + return icon_info; +} + +/** + * gtk_icon_info_copy: (skip) + * @icon_info: a #GtkIconInfo + * + * Make a copy of a #GtkIconInfo. + * + * Returns: (transfer full): the new GtkIconInfo + * + * Since: 2.4 + * + * Deprecated: 3.8: Use g_object_ref() + */ +GtkIconInfo * +gtk_icon_info_copy (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + return g_object_ref (icon_info); +} + +/** + * 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); + 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); + + g_clear_object (&icon_info->loadable); + g_slist_free_full (icon_info->emblem_infos, (GDestroyNotify) g_object_unref); + g_clear_object (&icon_info->pixbuf); + g_clear_object (&icon_info->proxy_pixbuf); + g_clear_object (&icon_info->cache_pixbuf); + g_clear_error (&icon_info->load_error); + + 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); + + gobject_class->finalize = gtk_icon_info_finalize; +} + +/** + * gtk_icon_info_get_base_size: + * @icon_info: a #GtkIconInfo + * + * Gets the base size for the icon. The base size + * is a size for the icon that was specified by + * the icon theme creator. This may be different + * than the actual size of image; an example of + * this is small emblem icons that can be attached + * to a larger icon. These icons will be given + * the same base size as the larger icons to which + * they are attached. + * + * Note that for scaled icons the base size does + * not include the base scale. + * + * Returns: the base size, or 0, if no base + * size is known for the icon. + * + * Since: 2.4 + */ +gint +gtk_icon_info_get_base_size (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, 0); + + return icon_info->dir_size; +} + +/** + * gtk_icon_info_get_base_scale: + * @icon_info: a #GtkIconInfo + * + * Gets the base scale for the icon. The base scale is a scale + * for the icon that was specified by the icon theme creator. + * For instance an icon drawn for a high-dpi screen with window + * scale 2 for a base size of 32 will be 64 pixels tall and have + * a base scale of 2. + * + * Returns: the base scale + * + * Since: 3.10 + */ +gint +gtk_icon_info_get_base_scale (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, 0); + + return icon_info->dir_scale; +} + +/** + * gtk_icon_info_get_filename: + * @icon_info: a #GtkIconInfo + * + * Gets the filename for the icon. If the %GTK_ICON_LOOKUP_USE_BUILTIN + * flag was passed to gtk_icon_theme_lookup_icon(), there may be no + * filename if a builtin icon is returned; in this case, you should + * use gtk_icon_info_get_builtin_pixbuf(). + * + * Returns: (nullable) (type filename): the filename for the icon, or %NULL + * if gtk_icon_info_get_builtin_pixbuf() should be used instead. + * The return value is owned by GTK+ and should not be modified + * or freed. + * + * Since: 2.4 + */ +const gchar * +gtk_icon_info_get_filename (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + + return icon_info->filename; +} + +/** + * gtk_icon_info_get_builtin_pixbuf: + * @icon_info: a #GtkIconInfo + * + * Gets the built-in image for this icon, if any. To allow GTK+ to use + * built in icon images, you must pass the %GTK_ICON_LOOKUP_USE_BUILTIN + * to gtk_icon_theme_lookup_icon(). + * + * Returns: (nullable) (transfer none): the built-in image pixbuf, or %NULL. + * No extra reference is added to the returned pixbuf, so if + * you want to keep it around, you must use g_object_ref(). + * The returned image must not be modified. + * + * Since: 2.4 + * + * Deprecated: 3.14: This function is deprecated, use + * gtk_icon_theme_add_resource_path() instead of builtin icons. + */ +GdkPixbuf * +gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + + if (icon_info->filename) + return NULL; + + return icon_info->cache_pixbuf; +} + +/** + * gtk_icon_info_is_symbolic: + * @icon_info: a #GtkIconInfo + * + * Checks if the icon is symbolic or not. This currently uses only + * the file name and not the file contents for determining this. + * This behaviour may change in the future. + * + * Returns: %TRUE if the icon is symbolic, %FALSE otherwise + * + * Since: 3.12 + */ +gboolean +gtk_icon_info_is_symbolic (GtkIconInfo *icon_info) +{ + gchar *icon_uri; + gboolean is_symbolic; + + g_return_val_if_fail (GTK_IS_ICON_INFO (icon_info), FALSE); + + icon_uri = NULL; + if (icon_info->icon_file) + icon_uri = g_file_get_uri (icon_info->icon_file); + + is_symbolic = (icon_uri != NULL) && (icon_uri_is_symbolic (icon_uri)); + g_free (icon_uri); + + return is_symbolic; +} + +static GdkPixbuf * +apply_emblems_to_pixbuf (GdkPixbuf *pixbuf, + GtkIconInfo *info) +{ + GdkPixbuf *icon = NULL; + gint w, h, pos; + GSList *l; + + if (info->emblem_infos == NULL) + return NULL; + + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + for (l = info->emblem_infos, pos = 0; l; l = l->next, pos++) + { + GtkIconInfo *emblem_info = l->data; + + if (icon_info_ensure_scale_and_pixbuf (emblem_info)) + { + GdkPixbuf *emblem = emblem_info->pixbuf; + gint ew, eh; + gint x = 0, y = 0; /* silence compiler */ + gdouble scale; + + ew = gdk_pixbuf_get_width (emblem); + eh = gdk_pixbuf_get_height (emblem); + if (ew >= w) + { + scale = 0.75; + ew = ew * 0.75; + eh = eh * 0.75; + } + else + scale = 1.0; + + switch (pos % 4) + { + case 0: + x = w - ew; + y = h - eh; + break; + case 1: + x = w - ew; + y = 0; + break; + case 2: + x = 0; + y = h - eh; + break; + case 3: + x = 0; + y = 0; + break; + } + + if (icon == NULL) + { + icon = gdk_pixbuf_copy (pixbuf); + if (icon == NULL) + break; + } + + gdk_pixbuf_composite (emblem, icon, x, y, ew, eh, x, y, + scale, scale, GDK_INTERP_BILINEAR, 255); + } + } + + return icon; +} + +/* Combine the icon with all emblems, the first emblem is placed + * in the southeast corner. Scale emblems to be at most 3/4 of the + * size of the icon itself. + */ +static void +apply_emblems (GtkIconInfo *info) +{ + GdkPixbuf *icon; + + if (info->emblems_applied) + return; + + icon = apply_emblems_to_pixbuf (info->pixbuf, info); + + if (icon) + { + g_object_unref (info->pixbuf); + info->pixbuf = icon; + 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. + */ +static gboolean +icon_info_ensure_scale_and_pixbuf (GtkIconInfo *icon_info) +{ + gint image_width, image_height, image_size; + gint scaled_desired_size; + GdkPixbuf *source_pixbuf; + gdouble dir_scale; + + if (icon_info->pixbuf) + { + apply_emblems (icon_info); + return TRUE; + } + + if (icon_info->load_error) + return FALSE; + + if (icon_info->icon_file && !icon_info->loadable) + icon_info->loadable = G_LOADABLE_ICON (g_file_icon_new (icon_info->icon_file)); + + scaled_desired_size = icon_info->desired_size * icon_info->desired_scale; + + dir_scale = icon_info->dir_scale; + + /* In many cases, the scale can be determined without actual access + * to the icon file. This is generally true when we have a size + * for the directory where the icon is; the image size doesn't + * matter in that case. + */ + if (icon_info->forced_size || + icon_info->dir_type == ICON_THEME_DIR_UNTHEMED) + icon_info->scale = -1; + else if (icon_info->dir_type == ICON_THEME_DIR_FIXED || + icon_info->dir_type == ICON_THEME_DIR_THRESHOLD) + icon_info->scale = icon_info->unscaled_scale; + else if (icon_info->dir_type == ICON_THEME_DIR_SCALABLE) + { + /* For svg icons, treat scalable directories as if they had + * a Scale= entry. In particular, this means + * spinners that are restriced to size 32 will loaded at size + * up to 64 with Scale=2. + */ + if (icon_info->is_svg) + dir_scale = icon_info->desired_scale; + + if (scaled_desired_size < icon_info->min_size * dir_scale) + icon_info->scale = (gdouble) icon_info->min_size / (gdouble) icon_info->dir_size; + else if (scaled_desired_size > icon_info->max_size * dir_scale) + icon_info->scale = (gdouble) icon_info->max_size / (gdouble) icon_info->dir_size; + else + icon_info->scale = (gdouble) scaled_desired_size / (icon_info->dir_size * dir_scale); + } + + /* At this point, we need to actually get the icon; either from the + * builtin image or by loading the file + */ + source_pixbuf = NULL; + if (icon_info->cache_pixbuf) + source_pixbuf = g_object_ref (icon_info->cache_pixbuf); + else if (icon_info->is_resource) + { + if (icon_info->is_svg) + { + gint size; + + if (icon_info->forced_size || icon_info->dir_type == ICON_THEME_DIR_UNTHEMED) + size = scaled_desired_size; + else + size = icon_info->dir_size * dir_scale * icon_info->scale; + + if (size == 0) + source_pixbuf = _gdk_pixbuf_new_from_resource_scaled (icon_info->filename, + icon_info->desired_scale, + &icon_info->load_error); + else + source_pixbuf = gdk_pixbuf_new_from_resource_at_scale (icon_info->filename, + size, size, TRUE, + &icon_info->load_error); + } + else + source_pixbuf = gdk_pixbuf_new_from_resource (icon_info->filename, + &icon_info->load_error); + } + else + { + GInputStream *stream; + + /* TODO: We should have a load_at_scale */ + stream = g_loadable_icon_load (icon_info->loadable, + scaled_desired_size, + NULL, NULL, + &icon_info->load_error); + if (stream) + { + /* SVG icons are a special case - we just immediately scale them + * to the desired size + */ + if (icon_info->is_svg) + { + gint size; + + if (icon_info->forced_size || icon_info->dir_type == ICON_THEME_DIR_UNTHEMED) + size = scaled_desired_size; + else + size = icon_info->dir_size * dir_scale * icon_info->scale; + if (size == 0) + source_pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, + icon_info->desired_scale, + NULL, + &icon_info->load_error); + else + source_pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream, + size, size, + TRUE, NULL, + &icon_info->load_error); + } + else + source_pixbuf = gdk_pixbuf_new_from_stream (stream, + NULL, + &icon_info->load_error); + g_object_unref (stream); + } + } + + if (!source_pixbuf) + { + static gboolean warn_about_load_failure = TRUE; + + if (warn_about_load_failure) + { + gchar *path; + + if (icon_info->is_resource) + path = g_strdup (icon_info->filename); + else if (G_IS_FILE (icon_info->loadable)) + path = g_file_get_path (G_FILE (icon_info->loadable)); + else + path = g_strdup ("icon theme"); + + g_warning ("Could not load a pixbuf from %s.\n" + "This may indicate that pixbuf loaders or the mime database could not be found.", + path); + g_free (path); + + warn_about_load_failure = FALSE; + } + + return FALSE; + } + + /* Do scale calculations that depend on the image size + */ + image_width = gdk_pixbuf_get_width (source_pixbuf); + image_height = gdk_pixbuf_get_height (source_pixbuf); + image_size = MAX (image_width, image_height); + + if (icon_info->is_svg) + icon_info->scale = image_size / 1000.; + else if (icon_info->scale < 0.0) + { + if (image_size > 0 && scaled_desired_size > 0) + icon_info->scale = (gdouble)scaled_desired_size / (gdouble)image_size; + else + icon_info->scale = 1.0; + + if (icon_info->dir_type == ICON_THEME_DIR_UNTHEMED && + !icon_info->forced_size) + icon_info->scale = MIN (icon_info->scale, 1.0); + } + + if (icon_info->is_svg) + icon_info->pixbuf = source_pixbuf; + else if (icon_info->scale == 1.0) + icon_info->pixbuf = source_pixbuf; + else + { + icon_info->pixbuf = gdk_pixbuf_scale_simple (source_pixbuf, + MAX (1, 0.5 + image_width * icon_info->scale), + MAX (1, 0.5 + image_height * icon_info->scale), + GDK_INTERP_BILINEAR); + g_object_unref (source_pixbuf); + } + + apply_emblems (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 from gtk_icon_theme_lookup_icon() + * @error: (allow-none): location to store error information on failure, + * or %NULL. + * + * Renders an icon previously looked up in an icon theme using + * gtk_icon_theme_lookup_icon(); the size will be based on the size + * passed to gtk_icon_theme_lookup_icon(). Note that the resulting + * pixbuf may not be exactly this size; an icon theme may have icons + * that differ slightly from their nominal sizes, and in addition GTK+ + * will avoid scaling icons that it considers sufficiently close to the + * requested size or for which the source image would have to be scaled + * up too far. (This maintains sharpness.). This behaviour can be changed + * by passing the %GTK_ICON_LOOKUP_FORCE_SIZE flag when obtaining + * the #GtkIconInfo. If this flag has been specified, the pixbuf + * returned by this function will be scaled to the exact size. + * + * Returns: (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: 2.4 + */ +GdkPixbuf * +gtk_icon_info_load_icon (GtkIconInfo *icon_info, + GError **error) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!icon_info_ensure_scale_and_pixbuf (icon_info)) + { + if (icon_info->load_error) + { + if (error) + *error = g_error_copy (icon_info->load_error); + } + else + { + g_set_error_literal (error, + GTK_ICON_THEME_ERROR, + GTK_ICON_THEME_NOT_FOUND, + _("Failed to load icon")); + } + + return NULL; + } + + /* 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; +} + +/** + * gtk_icon_info_load_surface: + * @icon_info: a #GtkIconInfo from gtk_icon_theme_lookup_icon() + * @for_window: (allow-none): #GdkWindow to optimize drawing for, or %NULL + * @error: (allow-none): location for error information on failure, or %NULL + * + * Renders an icon previously looked up in an icon theme using + * gtk_icon_theme_lookup_icon(); the size will be based on the size + * passed to gtk_icon_theme_lookup_icon(). Note that the resulting + * surface may not be exactly this size; an icon theme may have icons + * that differ slightly from their nominal sizes, and in addition GTK+ + * will avoid scaling icons that it considers sufficiently close to the + * requested size or for which the source image would have to be scaled + * up too far. (This maintains sharpness.). This behaviour can be changed + * by passing the %GTK_ICON_LOOKUP_FORCE_SIZE flag when obtaining + * the #GtkIconInfo. If this flag has been specified, the pixbuf + * returned by this function will be scaled to the exact size. + * + * Returns: (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 cairo_surface_destroy() to release your + * reference to the icon. + * + * Since: 3.10 + */ +cairo_surface_t * +gtk_icon_info_load_surface (GtkIconInfo *icon_info, + GdkWindow *for_window, + GError **error) +{ + GdkPixbuf *pixbuf; + cairo_surface_t *surface; + + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + pixbuf = gtk_icon_info_load_icon (icon_info, error); + + if (pixbuf == NULL) + return NULL; + + surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, icon_info->desired_scale, for_window); + g_object_unref (pixbuf); + + return surface; +} + +static void +load_icon_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GtkIconInfo *dup = task_data; + + (void)icon_info_ensure_scale_and_pixbuf (dup); + g_task_return_pointer (task, NULL, NULL); +} + +/** + * gtk_icon_info_load_icon_async: + * @icon_info: a #GtkIconInfo 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 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(). + * + * Returns: (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 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 gchar * +rgba_to_string_noalpha (const GdkRGBA *rgba) +{ + GdkRGBA color; + + color = *rgba; + color.alpha = 1.0; + + return gdk_rgba_to_string (&color); +} + +static void +rgba_to_pixel(const GdkRGBA *rgba, + guint8 pixel[4]) +{ + pixel[0] = rgba->red * 255; + pixel[1] = rgba->green * 255; + pixel[2] = rgba->blue * 255; + pixel[3] = 255; +} + +GdkPixbuf * +gtk_icon_theme_color_symbolic_pixbuf (GdkPixbuf *symbolic, + const GdkRGBA *fg_color, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color) +{ + int width, height, x, y, src_stride, dst_stride; + guchar *src_data, *dst_data; + guchar *src_row, *dst_row; + int alpha; + GdkPixbuf *colored; + guint8 fg_pixel[4], success_pixel[4], warning_pixel[4], error_pixel[4]; + + alpha = fg_color->alpha * 255; + + rgba_to_pixel (fg_color, fg_pixel); + rgba_to_pixel (success_color, success_pixel); + rgba_to_pixel (warning_color, warning_pixel); + rgba_to_pixel (error_color, error_pixel); + + width = gdk_pixbuf_get_width (symbolic); + height = gdk_pixbuf_get_height (symbolic); + + colored = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); + + src_stride = gdk_pixbuf_get_rowstride (symbolic); + src_data = gdk_pixbuf_get_pixels (symbolic); + + dst_data = gdk_pixbuf_get_pixels (colored); + dst_stride = gdk_pixbuf_get_rowstride (colored); + + for (y = 0; y < height; y++) + { + src_row = src_data + src_stride * y; + dst_row = dst_data + dst_stride * y; + for (x = 0; x < width; x++) + { + guint r, g, b, a; + int c1, c2, c3, c4; + + a = src_row[3]; + dst_row[3] = a * alpha / 255; + + if (a == 0) + { + dst_row[0] = 0; + dst_row[1] = 0; + dst_row[2] = 0; + } + else + { + c2 = src_row[0]; + c3 = src_row[1]; + c4 = src_row[2]; + + if (c2 == 0 && c3 == 0 && c4 == 0) + { + dst_row[0] = fg_pixel[0]; + dst_row[1] = fg_pixel[1]; + dst_row[2] = fg_pixel[2]; + } + else + { + c1 = 255 - c2 - c3 - c4; + + r = fg_pixel[0] * c1 + success_pixel[0] * c2 + warning_pixel[0] * c3 + error_pixel[0] * c4; + g = fg_pixel[1] * c1 + success_pixel[1] * c2 + warning_pixel[1] * c3 + error_pixel[1] * c4; + b = fg_pixel[2] * c1 + success_pixel[2] * c2 + warning_pixel[2] * c3 + error_pixel[2] * c4; + + dst_row[0] = r / 255; + dst_row[1] = g / 255; + dst_row[2] = b / 255; + } + } + + src_row += 4; + dst_row += 4; + } + } + + return colored; +} + +static GdkPixbuf * +gtk_icon_info_load_symbolic_png (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + GError **error) +{ + GdkRGBA fg_default = { 0.7450980392156863, 0.7450980392156863, 0.7450980392156863, 1.0}; + GdkRGBA success_default = { 0.3046921492332342,0.6015716792553597, 0.023437857633325704, 1.0}; + GdkRGBA warning_default = {0.9570458533607996, 0.47266346227206835, 0.2421911955443656, 1.0 }; + GdkRGBA error_default = { 0.796887159533074, 0 ,0, 1.0 }; + + if (!icon_info_ensure_scale_and_pixbuf (icon_info)) + { + if (icon_info->load_error) + { + if (error) + *error = g_error_copy (icon_info->load_error); + } + else + { + g_set_error_literal (error, + GTK_ICON_THEME_ERROR, + GTK_ICON_THEME_NOT_FOUND, + _("Failed to load icon")); + } + + return NULL; + } + + return gtk_icon_theme_color_symbolic_pixbuf (icon_info->pixbuf, + fg ? fg : &fg_default, + success_color ? success_color : &success_default, + warning_color ? warning_color : &warning_default, + error_color ? error_color : &error_default); +} + +static GdkPixbuf * +gtk_icon_info_load_symbolic_svg (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + GError **error) +{ + GInputStream *stream; + GdkPixbuf *pixbuf; + gchar *css_fg; + gchar *css_success; + gchar *css_warning; + gchar *css_error; + gchar *data; + gchar *width; + gchar *height; + gchar *file_data, *escaped_file_data; + gsize file_len; + gint symbolic_size; + double alpha; + gchar alphastr[G_ASCII_DTOSTR_BUF_SIZE]; + + alpha = fg->alpha; + + css_fg = rgba_to_string_noalpha (fg); + + css_success = css_warning = css_error = NULL; + + if (warning_color) + css_warning = rgba_to_string_noalpha (warning_color); + else + css_warning = g_strdup ("rgb(245,121,62)"); + + if (error_color) + css_error = rgba_to_string_noalpha (error_color); + else + css_error = g_strdup ("rgb(204,0,0)"); + + if (success_color) + css_success = rgba_to_string_noalpha (success_color); + else + css_success = g_strdup ("rgb(78,154,6)"); + + if (!g_file_load_contents (icon_info->icon_file, NULL, &file_data, &file_len, NULL, error)) + return NULL; + + if (!icon_info_ensure_scale_and_pixbuf (icon_info)) + { + g_propagate_error (error, icon_info->load_error); + icon_info->load_error = NULL; + g_free (css_fg); + g_free (css_warning); + g_free (css_error); + g_free (css_success); + g_free (file_data); + return NULL; + } + + if (icon_info->symbolic_width == 0 || + icon_info->symbolic_height == 0) + { + /* Fetch size from the original icon */ + stream = g_memory_input_stream_new_from_data (file_data, file_len, NULL); + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); + g_object_unref (stream); + + if (!pixbuf) + { + g_free (css_fg); + g_free (css_warning); + g_free (css_error); + g_free (css_success); + g_free (file_data); + return NULL; + } + + icon_info->symbolic_width = gdk_pixbuf_get_width (pixbuf); + icon_info->symbolic_height = gdk_pixbuf_get_height (pixbuf); + g_object_unref (pixbuf); + } + + symbolic_size = MAX (icon_info->symbolic_width, icon_info->symbolic_height); + + GTK_NOTE (ICONTHEME, + if (icon_info->dir_type == ICON_THEME_DIR_UNTHEMED) + g_message ("Symbolic icon %s is not in an icon theme directory", + icon_info->key.icon_names ? icon_info->key.icon_names[0] : icon_info->filename); + else if (icon_info->dir_size * icon_info->dir_scale != symbolic_size) + g_message ("Symbolic icon %s of size %d is in an icon theme directory of size %d", + icon_info->key.icon_names ? icon_info->key.icon_names[0] : icon_info->filename, + symbolic_size, + icon_info->dir_size * icon_info->dir_scale) + ); + + width = g_strdup_printf ("%d", icon_info->symbolic_width); + height = g_strdup_printf ("%d", icon_info->symbolic_height); + + escaped_file_data = g_base64_encode ((guchar *) file_data, file_len); + g_free (file_data); + + g_ascii_dtostr (alphastr, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (alpha, 0, 1)); + + data = g_strconcat ("\n" + "\n" + " \n" + " \n" + "", + NULL); + g_free (escaped_file_data); + g_free (css_fg); + g_free (css_warning); + g_free (css_error); + g_free (css_success); + g_free (width); + g_free (height); + + stream = g_memory_input_stream_new_from_data (data, -1, g_free); + pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream, + gdk_pixbuf_get_width (icon_info->pixbuf), + gdk_pixbuf_get_height (icon_info->pixbuf), + TRUE, + NULL, + error); + g_object_unref (stream); + + return pixbuf; +} + + +static GdkPixbuf * +gtk_icon_info_load_symbolic_internal (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + gboolean use_cache, + GError **error) +{ + GdkPixbuf *pixbuf; + SymbolicPixbufCache *symbolic_cache; + char *icon_uri; + + 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 (fg != NULL, NULL); + + icon_uri = g_file_get_uri (icon_info->icon_file); + if (g_str_has_suffix (icon_uri, ".symbolic.png")) + pixbuf = gtk_icon_info_load_symbolic_png (icon_info, fg, success_color, warning_color, error_color, error); + else + pixbuf = gtk_icon_info_load_symbolic_svg (icon_info, fg, success_color, warning_color, error_color, error); + + g_free (icon_uri); + + if (pixbuf != NULL) + { + GdkPixbuf *icon; + + icon = apply_emblems_to_pixbuf (pixbuf, icon_info); + if (icon != NULL) + { + g_object_unref (pixbuf); + pixbuf = icon; + } + + 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; +} + +/** + * gtk_icon_info_load_symbolic: + * @icon_info: a #GtkIconInfo + * @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) + * @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. + * + * Loads an icon, modifying it to match the system colours for the foreground, + * success, warning and error colors provided. If the icon is not a symbolic + * one, the function will return the result from gtk_icon_info_load_icon(). + * + * This allows loading symbolic icons that will match the system theme. + * + * Unless you are implementing a widget, you will want to use + * g_themed_icon_new_with_default_fallbacks() to load the icon. + * + * As implementation details, the icon loaded needs to be of SVG type, + * contain the “symbolic” term as the last component of the icon name, + * and use the “fg”, “success”, “warning” and “error” CSS styles in the + * SVG file itself. + * + * See the [Symbolic Icons Specification](http://www.freedesktop.org/wiki/SymbolicIcons) + * for more information about symbolic icons. + * + * Returns: (transfer full): a #GdkPixbuf representing the loaded icon + * + * Since: 3.0 + */ +GdkPixbuf * +gtk_icon_info_load_symbolic (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + gboolean *was_symbolic, + GError **error) +{ + gboolean is_symbolic; + + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (fg != NULL, NULL); + + is_symbolic = gtk_icon_info_is_symbolic (icon_info); + + if (was_symbolic) + *was_symbolic = is_symbolic; + + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); + + return gtk_icon_info_load_symbolic_internal (icon_info, + fg, success_color, + warning_color, error_color, + TRUE, + error); +} + +void +gtk_icon_theme_lookup_symbolic_colors (GtkCssStyle *style, + GdkRGBA *color_out, + GdkRGBA *success_out, + GdkRGBA *warning_out, + GdkRGBA *error_out) +{ + GtkCssValue *palette, *color; + const GdkRGBA *lookup; + + color = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_COLOR); + palette = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_PALETTE); + *color_out = *_gtk_css_rgba_value_get_rgba (color); + + lookup = gtk_css_palette_value_get_color (palette, "success"); + if (lookup) + *success_out = *lookup; + else + *success_out = *color_out; + + lookup = gtk_css_palette_value_get_color (palette, "warning"); + if (lookup) + *warning_out = *lookup; + else + *warning_out = *color_out; + + lookup = gtk_css_palette_value_get_color (palette, "error"); + if (lookup) + *error_out = *lookup; + else + *error_out = *color_out; +} + +/** + * gtk_icon_info_load_symbolic_for_context: + * @icon_info: a #GtkIconInfo + * @context: a #GtkStyleContext + * @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. + * + * Loads an icon, modifying it to match the system colors for the foreground, + * success, warning and error colors provided. If the icon is not a symbolic + * one, the function will return the result from gtk_icon_info_load_icon(). + * This function uses the regular foreground color and the symbolic colors + * with the names “success_color”, “warning_color” and “error_color” from + * the context. + * + * This allows loading symbolic icons that will match the system theme. + * + * See gtk_icon_info_load_symbolic() for more details. + * + * Returns: (transfer full): a #GdkPixbuf representing the loaded icon + * + * Since: 3.0 + */ +GdkPixbuf * +gtk_icon_info_load_symbolic_for_context (GtkIconInfo *icon_info, + GtkStyleContext *context, + gboolean *was_symbolic, + GError **error) +{ + GdkRGBA fg; + GdkRGBA success_color; + GdkRGBA warning_color; + GdkRGBA error_color; + gboolean is_symbolic; + + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + + is_symbolic = gtk_icon_info_is_symbolic (icon_info); + + if (was_symbolic) + *was_symbolic = is_symbolic; + + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); + + gtk_icon_theme_lookup_symbolic_colors (gtk_style_context_lookup_style (context), + &fg, &success_color, + &warning_color, &error_color); + + return gtk_icon_info_load_symbolic_internal (icon_info, + &fg, &success_color, + &warning_color, &error_color, + 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 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; + 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); + + data->is_symbolic = gtk_icon_info_is_symbolic (icon_info); + + 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 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(). + * + * Returns: (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 = 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 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 fg; + GdkRGBA success_color; + GdkRGBA warning_color; + GdkRGBA error_color; + + g_return_if_fail (icon_info != NULL); + g_return_if_fail (context != NULL); + + gtk_icon_theme_lookup_symbolic_colors (gtk_style_context_lookup_style (context), + &fg, &success_color, + &warning_color, &error_color); + + gtk_icon_info_load_symbolic_async (icon_info, + &fg, &success_color, + &warning_color, &error_color, + cancellable, callback, user_data); +} + +/** + * gtk_icon_info_load_symbolic_for_context_finish: + * @icon_info: a #GtkIconInfo 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(). + * + * Returns: (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); +} + +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; +} + +/** + * gtk_icon_info_load_symbolic_for_style: + * @icon_info: a #GtkIconInfo + * @style: a #GtkStyle to take the colors from + * @state: the widget state to use for colors + * @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. + * + * Loads an icon, modifying it to match the system colours for the foreground, + * success, warning and error colors provided. If the icon is not a symbolic + * one, the function will return the result from gtk_icon_info_load_icon(). + * + * This allows loading symbolic icons that will match the system theme. + * + * See gtk_icon_info_load_symbolic() for more details. + * + * Returns: (transfer full): a #GdkPixbuf representing the loaded icon + * + * Since: 3.0 + * + * Deprecated: 3.0: Use gtk_icon_info_load_symbolic_for_context() instead + */ +GdkPixbuf * +gtk_icon_info_load_symbolic_for_style (GtkIconInfo *icon_info, + GtkStyle *style, + GtkStateType state, + gboolean *was_symbolic, + GError **error) +{ + GdkColor color; + GdkRGBA fg; + GdkRGBA success_color; + GdkRGBA *success_colorp; + GdkRGBA warning_color; + GdkRGBA *warning_colorp; + GdkRGBA error_color; + GdkRGBA *error_colorp; + gboolean is_symbolic; + + g_return_val_if_fail (icon_info != NULL, NULL); + g_return_val_if_fail (style != NULL, NULL); + + is_symbolic = gtk_icon_info_is_symbolic (icon_info); + + if (was_symbolic) + *was_symbolic = is_symbolic; + + if (!is_symbolic) + return gtk_icon_info_load_icon (icon_info, error); + + color_to_rgba (&style->fg[state], &fg); + + success_colorp = warning_colorp = error_colorp = NULL; + + if (gtk_style_lookup_color (style, "success_color", &color)) + success_colorp = color_to_rgba (&color, &success_color); + + if (gtk_style_lookup_color (style, "warning_color", &color)) + warning_colorp = color_to_rgba (&color, &warning_color); + + 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); +} + +/** + * gtk_icon_info_set_raw_coordinates: + * @icon_info: a #GtkIconInfo + * @raw_coordinates: whether the coordinates of embedded rectangles + * and attached points should be returned in their original + * (unscaled) form. + * + * Sets whether the coordinates returned by gtk_icon_info_get_embedded_rect() + * and gtk_icon_info_get_attach_points() should be returned in their + * original form as specified in the icon theme, instead of scaled + * appropriately for the pixbuf returned by gtk_icon_info_load_icon(). + * + * Raw coordinates are somewhat strange; they are specified to be with + * respect to the unscaled pixmap for PNG and XPM icons, but for SVG + * icons, they are in a 1000x1000 coordinate space that is scaled + * to the final size of the icon. You can determine if the icon is an SVG + * icon by using gtk_icon_info_get_filename(), and seeing if it is non-%NULL + * and ends in “.svg”. + * + * This function is provided primarily to allow compatibility wrappers + * for older API's, and is not expected to be useful for applications. + * + * Since: 2.4 + * + * Deprecated: 3.14: Embedded rectangles and attachment points are deprecated + */ +void +gtk_icon_info_set_raw_coordinates (GtkIconInfo *icon_info, + gboolean raw_coordinates) +{ +} + +/** + * gtk_icon_info_get_embedded_rect: + * @icon_info: a #GtkIconInfo + * @rectangle: (out): #GdkRectangle in which to store embedded + * rectangle coordinates; coordinates are only stored + * when this function returns %TRUE. + * + * This function is deprecated and always returns %FALSE. + * + * Returns: %FALSE + * + * Since: 2.4 + * + * Deprecated: 3.14: Embedded rectangles are deprecated + */ +gboolean +gtk_icon_info_get_embedded_rect (GtkIconInfo *icon_info, + GdkRectangle *rectangle) +{ + return FALSE; +} + +/** + * gtk_icon_info_get_attach_points: + * @icon_info: a #GtkIconInfo + * @points: (allow-none) (array length=n_points) (out): location to store pointer + * to an array of points, or %NULL free the array of points with g_free(). + * @n_points: (allow-none): location to store the number of points in @points, + * or %NULL + * + * This function is deprecated and always returns %FALSE. + * + * Returns: %FALSE + * + * Since: 2.4 + * + * Deprecated: 3.14: Attachment points are deprecated + */ +gboolean +gtk_icon_info_get_attach_points (GtkIconInfo *icon_info, + GdkPoint **points, + gint *n_points) +{ + return FALSE; +} + +/** + * gtk_icon_info_get_display_name: + * @icon_info: a #GtkIconInfo + * + * This function is deprecated and always returns %NULL. + * + * Returns: %NULL + * + * Since: 2.4 + * + * Deprecated: 3.14: Display names are deprecated + */ +const gchar * +gtk_icon_info_get_display_name (GtkIconInfo *icon_info) +{ + return NULL; +} + +/* + * Builtin icons + */ + + +/** + * gtk_icon_theme_add_builtin_icon: + * @icon_name: the name of the icon to register + * @size: the size in pixels at which to register the icon (different + * images can be registered for the same icon name at different sizes.) + * @pixbuf: #GdkPixbuf that contains the image to use for @icon_name + * + * Registers a built-in icon for icon theme lookups. The idea + * of built-in icons is to allow an application or library + * that uses themed icons to function requiring files to + * be present in the file system. For instance, the default + * images for all of GTK+’s stock icons are registered + * as built-icons. + * + * In general, if you use gtk_icon_theme_add_builtin_icon() + * you should also install the icon in the icon theme, so + * that the icon is generally available. + * + * This function will generally be used with pixbufs loaded + * via gdk_pixbuf_new_from_inline(). + * + * Since: 2.4 + * + * Deprecated: 3.14: Use gtk_icon_theme_add_resource_path() + * to add application-specific icons to the icon theme. + */ +void +gtk_icon_theme_add_builtin_icon (const gchar *icon_name, + gint size, + GdkPixbuf *pixbuf) +{ + BuiltinIcon *default_icon; + GSList *icons; + gpointer key; + + g_return_if_fail (icon_name != NULL); + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + + if (!icon_theme_builtin_icons) + icon_theme_builtin_icons = g_hash_table_new (g_str_hash, g_str_equal); + + icons = g_hash_table_lookup (icon_theme_builtin_icons, icon_name); + if (!icons) + key = g_strdup (icon_name); + else + key = (gpointer)icon_name; /* Won't get stored */ + + default_icon = g_new (BuiltinIcon, 1); + default_icon->size = size; + default_icon->pixbuf = g_object_ref (pixbuf); + icons = g_slist_prepend (icons, default_icon); + + /* Replaces value, leaves key untouched + */ + g_hash_table_insert (icon_theme_builtin_icons, key, icons); +} + +/* Look up a builtin icon; the min_difference_p and + * has_larger_p out parameters allow us to combine + * this lookup with searching through the actual directories + * of the “hicolor” icon theme. See theme_lookup_icon() + * for how they are used. + */ +static BuiltinIcon * +find_builtin_icon (const gchar *icon_name, + gint size, + gint scale, + gint *min_difference_p) +{ + GSList *icons = NULL; + gint min_difference = G_MAXINT; + gboolean has_larger = FALSE; + BuiltinIcon *min_icon = NULL; + + if (!icon_theme_builtin_icons) + return NULL; + + size *= scale; + + icons = g_hash_table_lookup (icon_theme_builtin_icons, icon_name); + + while (icons) + { + BuiltinIcon *default_icon = icons->data; + int min, max, difference; + gboolean smaller; + + min = default_icon->size - 2; + max = default_icon->size + 2; + smaller = size < min; + if (size < min) + difference = min - size; + else if (size > max) + difference = size - max; + else + difference = 0; + + if (difference == 0) + { + min_difference = 0; + min_icon = default_icon; + break; + } + + if (!has_larger) + { + if (difference < min_difference || smaller) + { + min_difference = difference; + min_icon = default_icon; + has_larger = smaller; + } + } + else + { + if (difference < min_difference && smaller) + { + min_difference = difference; + min_icon = default_icon; + } + } + + icons = icons->next; + } + + if (min_difference_p) + *min_difference_p = min_difference; + + return min_icon; +} + +/** + * gtk_icon_theme_lookup_by_gicon: + * @icon_theme: a #GtkIconTheme + * @icon: the #GIcon to look up + * @size: desired icon size + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up an icon and returns a #GtkIconInfo containing information + * such as the filename of the icon. The icon can then be rendered + * into a pixbuf using gtk_icon_info_load_icon(). + * + * When rendering on displays with high pixel densities you should not + * use a @size multiplied by the scaling factor returned by functions + * like gdk_window_get_scale_factor(). Instead, you should use + * gtk_icon_theme_lookup_by_gicon_for_scale(), as the assets loaded + * for a given scaling factor may be different. + * + * Returns: (nullable) (transfer full): a #GtkIconInfo containing + * information about the icon, or %NULL if the icon wasn’t + * found. Unref with g_object_unref() + * + * Since: 2.14 + */ +GtkIconInfo * +gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, + GIcon *icon, + gint size, + GtkIconLookupFlags flags) +{ + return gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, icon, + size, 1, flags); +} + + +/** + * gtk_icon_theme_lookup_by_gicon_for_scale: + * @icon_theme: a #GtkIconTheme + * @icon: the #GIcon to look up + * @size: desired icon size + * @scale: the desired scale + * @flags: flags modifying the behavior of the icon lookup + * + * Looks up an icon and returns a #GtkIconInfo containing information + * such as the filename of the icon. The icon can then be rendered into + * a pixbuf using gtk_icon_info_load_icon(). + * + * Returns: (nullable) (transfer full): a #GtkIconInfo containing + * information about the icon, or %NULL if the icon wasn’t + * found. Unref with g_object_unref() + * + * Since: 3.10 + */ +GtkIconInfo * +gtk_icon_theme_lookup_by_gicon_for_scale (GtkIconTheme *icon_theme, + GIcon *icon, + gint size, + gint scale, + GtkIconLookupFlags flags) +{ + GtkIconInfo *info; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (G_IS_ICON (icon), NULL); + g_warn_if_fail ((flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) == 0); + + if (GDK_IS_PIXBUF (icon)) + { + GdkPixbuf *pixbuf; + + pixbuf = GDK_PIXBUF (icon); + + if ((flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0) + { + gint width, height, max; + gdouble pixbuf_scale; + GdkPixbuf *scaled; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + max = MAX (width, height); + pixbuf_scale = (gdouble) size * scale / (gdouble) max; + + scaled = gdk_pixbuf_scale_simple (pixbuf, + 0.5 + width * pixbuf_scale, + 0.5 + height * pixbuf_scale, + GDK_INTERP_BILINEAR); + + info = gtk_icon_info_new_for_pixbuf (icon_theme, scaled); + + g_object_unref (scaled); + } + else + { + info = gtk_icon_info_new_for_pixbuf (icon_theme, pixbuf); + } + + return info; + } + else if (G_IS_FILE_ICON (icon)) + { + GFile *file = g_file_icon_get_file (G_FILE_ICON (icon)); + + info = gtk_icon_info_new_for_file (file, size, scale); + info->forced_size = (flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0; + + return info; + } + else if (G_IS_LOADABLE_ICON (icon)) + { + info = icon_info_new (ICON_THEME_DIR_UNTHEMED, size, 1); + info->loadable = G_LOADABLE_ICON (g_object_ref (icon)); + info->is_svg = FALSE; + info->desired_size = size; + info->desired_scale = scale; + info->forced_size = (flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0; + + return info; + } + else if (G_IS_THEMED_ICON (icon)) + { + const gchar **names; + + names = (const gchar **)g_themed_icon_get_names (G_THEMED_ICON (icon)); + info = gtk_icon_theme_choose_icon_for_scale (icon_theme, names, size, scale, flags); + + return info; + } + else if (G_IS_EMBLEMED_ICON (icon)) + { + GIcon *base, *emblem; + GList *list, *l; + GtkIconInfo *base_info, *emblem_info; + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (GTK_IS_NUMERABLE_ICON (icon)) + _gtk_numerable_icon_set_background_icon_size (GTK_NUMERABLE_ICON (icon), size / 2); +G_GNUC_END_IGNORE_DEPRECATIONS + + base = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon)); + base_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, base, size, scale, 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) + { + emblem = g_emblem_get_icon (G_EMBLEM (l->data)); + /* always force size for emblems */ + emblem_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, emblem, size / 2, scale, flags | GTK_ICON_LOOKUP_FORCE_SIZE); + if (emblem_info) + info->emblem_infos = g_slist_prepend (info->emblem_infos, emblem_info); + } + + return info; + } + else + return NULL; + } + + return NULL; +} + +/** + * gtk_icon_info_new_for_pixbuf: + * @icon_theme: a #GtkIconTheme + * @pixbuf: the pixbuf to wrap in a #GtkIconInfo + * + * Creates a #GtkIconInfo for a #GdkPixbuf. + * + * Returns: (transfer full): a #GtkIconInfo + * + * Since: 2.14 + */ +GtkIconInfo * +gtk_icon_info_new_for_pixbuf (GtkIconTheme *icon_theme, + GdkPixbuf *pixbuf) +{ + GtkIconInfo *info; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + info = icon_info_new (ICON_THEME_DIR_UNTHEMED, 0, 1); + info->pixbuf = g_object_ref (pixbuf); + info->scale = 1.0; + + return info; +} + +GtkIconInfo * +gtk_icon_info_new_for_file (GFile *file, + gint size, + gint scale) +{ + GtkIconInfo *info; + + info = icon_info_new (ICON_THEME_DIR_UNTHEMED, size, 1); + info->loadable = G_LOADABLE_ICON (g_file_icon_new (file)); + info->icon_file = g_object_ref (file); + info->is_resource = g_file_has_uri_scheme (file, "resource"); + + if (info->is_resource) + { + gchar *uri; + + uri = g_file_get_uri (file); + info->filename = g_strdup (uri + 11); /* resource:// */ + g_free (uri); + } + else + { + info->filename = g_file_get_path (file); + } + + info->is_svg = suffix_from_name (info->filename) == ICON_SUFFIX_SVG; + + info->desired_size = size; + info->desired_scale = scale; + info->forced_size = FALSE; + + return info; +} diff --git a/src/st/st-icon-theme.h b/src/st/st-icon-theme.h new file mode 100644 index 000000000..f35194f65 --- /dev/null +++ b/src/st/st-icon-theme.h @@ -0,0 +1,376 @@ +/* GtkIconTheme - a loader for icon themes + * gtk-icon-loader.h Copyright (C) 2002, 2003 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * 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, see . + */ + +#ifndef __GTK_ICON_THEME_H__ +#define __GTK_ICON_THEME_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_ICON_INFO (gtk_icon_info_get_type ()) +#define GTK_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ICON_INFO, GtkIconInfo)) +#define GTK_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ICON_INFO, GtkIconInfoClass)) +#define GTK_IS_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ICON_INFO)) +#define GTK_IS_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ICON_INFO)) +#define GTK_ICON_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ICON_INFO, GtkIconInfoClass)) + +#define GTK_TYPE_ICON_THEME (gtk_icon_theme_get_type ()) +#define GTK_ICON_THEME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ICON_THEME, GtkIconTheme)) +#define GTK_ICON_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ICON_THEME, GtkIconThemeClass)) +#define GTK_IS_ICON_THEME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ICON_THEME)) +#define GTK_IS_ICON_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ICON_THEME)) +#define GTK_ICON_THEME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ICON_THEME, GtkIconThemeClass)) + +/** + * GtkIconInfo: + * + * Contains information found when looking up an icon in + * an icon theme. + */ +typedef struct _GtkIconInfo GtkIconInfo; +typedef struct _GtkIconInfoClass GtkIconInfoClass; +typedef struct _GtkIconTheme GtkIconTheme; +typedef struct _GtkIconThemeClass GtkIconThemeClass; +typedef struct _GtkIconThemePrivate GtkIconThemePrivate; + +/** + * GtkIconTheme: + * + * Acts as a database of information about an icon theme. + * Normally, you retrieve the icon theme for a particular + * screen using gtk_icon_theme_get_for_screen() and it + * will contain information about current icon theme for + * that screen, but you can also create a new #GtkIconTheme + * object and set the icon theme name explicitly using + * gtk_icon_theme_set_custom_theme(). + */ +struct _GtkIconTheme +{ + /*< private >*/ + GObject parent_instance; + + GtkIconThemePrivate *priv; +}; + +/** + * GtkIconThemeClass: + * @parent_class: The parent class. + * @changed: Signal emitted when the current icon theme is switched or + * GTK+ detects that a change has occurred in the contents of the + * current icon theme. + */ +struct _GtkIconThemeClass +{ + GObjectClass parent_class; + + /*< public >*/ + + void (* changed) (GtkIconTheme *icon_theme); + + /*< private >*/ + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +/** + * GtkIconLookupFlags: + * @GTK_ICON_LOOKUP_NO_SVG: Never get SVG icons, even if gdk-pixbuf + * supports them. Cannot be used together with %GTK_ICON_LOOKUP_FORCE_SVG. + * @GTK_ICON_LOOKUP_FORCE_SVG: Get SVG icons, even if gdk-pixbuf + * doesn’t support them. + * Cannot be used together with %GTK_ICON_LOOKUP_NO_SVG. + * @GTK_ICON_LOOKUP_USE_BUILTIN: When passed to + * gtk_icon_theme_lookup_icon() includes builtin icons + * as well as files. For a builtin icon, gtk_icon_info_get_filename() + * is %NULL and you need to call gtk_icon_info_get_builtin_pixbuf(). + * @GTK_ICON_LOOKUP_GENERIC_FALLBACK: Try to shorten icon name at '-' + * characters before looking at inherited themes. This flag is only + * supported in functions that take a single icon name. For more general + * fallback, see gtk_icon_theme_choose_icon(). Since 2.12. + * @GTK_ICON_LOOKUP_FORCE_SIZE: Always get the icon scaled to the + * requested size. Since 2.14. + * @GTK_ICON_LOOKUP_FORCE_REGULAR: Try to always load regular icons, even + * when symbolic icon names are given. Since 3.14. + * @GTK_ICON_LOOKUP_FORCE_SYMBOLIC: Try to always load symbolic icons, even + * when regular icon names are given. Since 3.14. + * @GTK_ICON_LOOKUP_DIR_LTR: Try to load a variant of the icon for left-to-right + * text direction. Since 3.14. + * @GTK_ICON_LOOKUP_DIR_RTL: Try to load a variant of the icon for right-to-left + * text direction. Since 3.14. + * + * Used to specify options for gtk_icon_theme_lookup_icon() + */ +typedef enum +{ + GTK_ICON_LOOKUP_NO_SVG = 1 << 0, + GTK_ICON_LOOKUP_FORCE_SVG = 1 << 1, + GTK_ICON_LOOKUP_USE_BUILTIN = 1 << 2, + GTK_ICON_LOOKUP_GENERIC_FALLBACK = 1 << 3, + GTK_ICON_LOOKUP_FORCE_SIZE = 1 << 4, + GTK_ICON_LOOKUP_FORCE_REGULAR = 1 << 5, + GTK_ICON_LOOKUP_FORCE_SYMBOLIC = 1 << 6, + GTK_ICON_LOOKUP_DIR_LTR = 1 << 7, + GTK_ICON_LOOKUP_DIR_RTL = 1 << 8 +} GtkIconLookupFlags; + +/** + * GTK_ICON_THEME_ERROR: + * + * The #GQuark used for #GtkIconThemeError errors. + */ +#define GTK_ICON_THEME_ERROR gtk_icon_theme_error_quark () + +/** + * GtkIconThemeError: + * @GTK_ICON_THEME_NOT_FOUND: The icon specified does not exist in the theme + * @GTK_ICON_THEME_FAILED: An unspecified error occurred. + * + * Error codes for GtkIconTheme operations. + **/ +typedef enum { + GTK_ICON_THEME_NOT_FOUND, + GTK_ICON_THEME_FAILED +} GtkIconThemeError; + +GDK_AVAILABLE_IN_ALL +GQuark gtk_icon_theme_error_quark (void); + +GDK_AVAILABLE_IN_ALL +GType gtk_icon_theme_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GtkIconTheme *gtk_icon_theme_new (void); +GDK_AVAILABLE_IN_ALL +GtkIconTheme *gtk_icon_theme_get_default (void); +GDK_AVAILABLE_IN_ALL +GtkIconTheme *gtk_icon_theme_get_for_screen (GdkScreen *screen); +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_set_screen (GtkIconTheme *icon_theme, + GdkScreen *screen); + +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme, + const gchar *path[], + gint n_elements); +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_get_search_path (GtkIconTheme *icon_theme, + gchar **path[], + gint *n_elements); +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_append_search_path (GtkIconTheme *icon_theme, + const gchar *path); +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_prepend_search_path (GtkIconTheme *icon_theme, + const gchar *path); + +GDK_AVAILABLE_IN_3_14 +void gtk_icon_theme_add_resource_path (GtkIconTheme *icon_theme, + const gchar *path); + +GDK_AVAILABLE_IN_ALL +void gtk_icon_theme_set_custom_theme (GtkIconTheme *icon_theme, + const gchar *theme_name); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_icon_theme_has_icon (GtkIconTheme *icon_theme, + const gchar *icon_name); +GDK_AVAILABLE_IN_ALL +gint *gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme, + const gchar *icon_name); +GDK_AVAILABLE_IN_ALL +GtkIconInfo * gtk_icon_theme_lookup_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags); +GDK_AVAILABLE_IN_3_10 +GtkIconInfo * gtk_icon_theme_lookup_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GtkIconLookupFlags flags); + +GDK_AVAILABLE_IN_ALL +GtkIconInfo * gtk_icon_theme_choose_icon (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + GtkIconLookupFlags flags); +GDK_AVAILABLE_IN_3_10 +GtkIconInfo * gtk_icon_theme_choose_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_names[], + gint size, + gint scale, + GtkIconLookupFlags flags); +GDK_AVAILABLE_IN_ALL +GdkPixbuf * gtk_icon_theme_load_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags, + GError **error); +GDK_AVAILABLE_IN_3_10 +GdkPixbuf * gtk_icon_theme_load_icon_for_scale (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GtkIconLookupFlags flags, + GError **error); +GDK_AVAILABLE_IN_3_10 +cairo_surface_t * gtk_icon_theme_load_surface (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + gint scale, + GdkWindow *for_window, + GtkIconLookupFlags flags, + GError **error); + +GDK_AVAILABLE_IN_ALL +GtkIconInfo * gtk_icon_theme_lookup_by_gicon (GtkIconTheme *icon_theme, + GIcon *icon, + gint size, + GtkIconLookupFlags flags); +GDK_AVAILABLE_IN_3_10 +GtkIconInfo * gtk_icon_theme_lookup_by_gicon_for_scale (GtkIconTheme *icon_theme, + GIcon *icon, + gint size, + gint scale, + GtkIconLookupFlags flags); + + +GDK_AVAILABLE_IN_ALL +GList * gtk_icon_theme_list_icons (GtkIconTheme *icon_theme, + const gchar *context); +GDK_AVAILABLE_IN_ALL +GList * gtk_icon_theme_list_contexts (GtkIconTheme *icon_theme); +GDK_AVAILABLE_IN_ALL +char * gtk_icon_theme_get_example_icon_name (GtkIconTheme *icon_theme); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_icon_theme_rescan_if_needed (GtkIconTheme *icon_theme); + +GDK_DEPRECATED_IN_3_14_FOR(gtk_icon_theme_add_resource_path) +void gtk_icon_theme_add_builtin_icon (const gchar *icon_name, + gint size, + GdkPixbuf *pixbuf); + +GDK_AVAILABLE_IN_ALL +GType gtk_icon_info_get_type (void) G_GNUC_CONST; +GDK_DEPRECATED_IN_3_8_FOR(g_object_ref) +GtkIconInfo * gtk_icon_info_copy (GtkIconInfo *icon_info); +GDK_DEPRECATED_IN_3_8_FOR(g_object_unref) +void gtk_icon_info_free (GtkIconInfo *icon_info); + +GDK_AVAILABLE_IN_ALL +GtkIconInfo * gtk_icon_info_new_for_pixbuf (GtkIconTheme *icon_theme, + GdkPixbuf *pixbuf); + +GDK_AVAILABLE_IN_ALL +gint gtk_icon_info_get_base_size (GtkIconInfo *icon_info); +GDK_AVAILABLE_IN_3_10 +gint gtk_icon_info_get_base_scale (GtkIconInfo *icon_info); +GDK_AVAILABLE_IN_ALL +const gchar * gtk_icon_info_get_filename (GtkIconInfo *icon_info); +GDK_DEPRECATED_IN_3_14 +GdkPixbuf * gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info); +GDK_AVAILABLE_IN_3_12 +gboolean gtk_icon_info_is_symbolic (GtkIconInfo *icon_info); +GDK_AVAILABLE_IN_ALL +GdkPixbuf * gtk_icon_info_load_icon (GtkIconInfo *icon_info, + GError **error); +GDK_AVAILABLE_IN_3_10 +cairo_surface_t * gtk_icon_info_load_surface (GtkIconInfo *icon_info, + GdkWindow *for_window, + GError **error); +GDK_AVAILABLE_IN_3_8 +void gtk_icon_info_load_icon_async (GtkIconInfo *icon_info, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDK_AVAILABLE_IN_3_8 +GdkPixbuf * gtk_icon_info_load_icon_finish (GtkIconInfo *icon_info, + GAsyncResult *res, + GError **error); +GDK_AVAILABLE_IN_ALL +GdkPixbuf * gtk_icon_info_load_symbolic (GtkIconInfo *icon_info, + const GdkRGBA *fg, + const GdkRGBA *success_color, + const GdkRGBA *warning_color, + const GdkRGBA *error_color, + gboolean *was_symbolic, + GError **error); +GDK_AVAILABLE_IN_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); +GDK_AVAILABLE_IN_3_8 +GdkPixbuf * gtk_icon_info_load_symbolic_finish (GtkIconInfo *icon_info, + GAsyncResult *res, + gboolean *was_symbolic, + GError **error); +GDK_AVAILABLE_IN_ALL +GdkPixbuf * gtk_icon_info_load_symbolic_for_context (GtkIconInfo *icon_info, + GtkStyleContext *context, + gboolean *was_symbolic, + GError **error); +GDK_AVAILABLE_IN_3_8 +void gtk_icon_info_load_symbolic_for_context_async (GtkIconInfo *icon_info, + GtkStyleContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDK_AVAILABLE_IN_3_8 +GdkPixbuf * gtk_icon_info_load_symbolic_for_context_finish (GtkIconInfo *icon_info, + GAsyncResult *res, + gboolean *was_symbolic, + GError **error); +GDK_DEPRECATED_IN_3_0_FOR(gtk_icon_info_load_symbol_for_context) +GdkPixbuf * gtk_icon_info_load_symbolic_for_style (GtkIconInfo *icon_info, + GtkStyle *style, + GtkStateType state, + gboolean *was_symbolic, + GError **error); +GDK_DEPRECATED_IN_3_14 +void gtk_icon_info_set_raw_coordinates (GtkIconInfo *icon_info, + gboolean raw_coordinates); + +GDK_DEPRECATED_IN_3_14 +gboolean gtk_icon_info_get_embedded_rect (GtkIconInfo *icon_info, + GdkRectangle *rectangle); +GDK_DEPRECATED_IN_3_14 +gboolean gtk_icon_info_get_attach_points (GtkIconInfo *icon_info, + GdkPoint **points, + gint *n_points); +GDK_DEPRECATED_IN_3_14 +const gchar * gtk_icon_info_get_display_name (GtkIconInfo *icon_info); + +G_END_DECLS + +#endif /* __GTK_ICON_THEME_H__ */