From a72aed4ea22a9951742919e00ed7c47f14731001 Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Wed, 2 Jul 2003 22:44:09 +0000 Subject: [PATCH] Implement a loader for named themed icon based on from gnome-desktop Wed Jul 2 18:00:56 2003 Owen Taylor * gtk/gtkicontheme.[ch]: Implement a loader for named themed icon based on from gnome-desktop library by Alex Larsson. * gtk/gtkiconthemeparser.[ch]: .ini file parsing code from gnome-desktop. * gtk/gtkiconfactory.[ch]: Add gtk_icon_source_set/get_icon_name() to allow stock icons to be based off of named theme icons. * gtk/gtkiconfactory.c: Rework sources so that the source is *either* a pixbuf, or a filename, or an icon name, instead of the pixbuf/filename mix it was before. Put a workaround for get_pixbuf() so that it can return the filename pixbuf, e.g, for render_icon(). * gtk/gtkiconfactory.c: Make the default setup use themed icons, and add builtin icons to the default icon theme for all the standard pixbufs, so we don't rely on actually having an icon theme on disk. * gtk/gtkrc.c: Add support for @"icon-name" to specify a themed icon for a stock icon source. * tests/Makefile.am test/testicontheme.c: Add a test program from gnome-desktop. * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add Net/IconThemeName / gtk-icon-theme-name setting. * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually update the icon cache serial so we don't continually * gtk/gtkwidget.c: Fix a couple of references in doc comments to ::direction_set that should have been to ::direction-changed --- ChangeLog | 40 + ChangeLog.pre-2-10 | 40 + ChangeLog.pre-2-4 | 40 + ChangeLog.pre-2-6 | 40 + ChangeLog.pre-2-8 | 40 + gdk/x11/gdkevents-x11.c | 3 +- gtk/Makefile.am | 5 + gtk/gtk.h | 1 + gtk/gtkiconfactory.c | 1173 +++++++++--------- gtk/gtkiconfactory.h | 9 +- gtk/gtkicontheme.c | 2535 ++++++++++++++++++++++++++++++++++++++ gtk/gtkicontheme.h | 161 +++ gtk/gtkiconthemeparser.c | 854 +++++++++++++ gtk/gtkiconthemeparser.h | 86 ++ gtk/gtkrc.c | 32 +- gtk/gtksettings.c | 9 + gtk/gtkwidget.c | 4 +- 17 files changed, 4507 insertions(+), 565 deletions(-) create mode 100644 gtk/gtkicontheme.c create mode 100644 gtk/gtkicontheme.h create mode 100644 gtk/gtkiconthemeparser.c create mode 100644 gtk/gtkiconthemeparser.h diff --git a/ChangeLog b/ChangeLog index 48fd950b12..4f48f42119 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor + + * gtk/gtkicontheme.[ch]: Implement a loader for + named themed icon based on from gnome-desktop library + by Alex Larsson. + + * gtk/gtkiconthemeparser.[ch]: .ini file parsing code + from gnome-desktop. + + * gtk/gtkiconfactory.[ch]: Add + gtk_icon_source_set/get_icon_name() to allow stock icons + to be based off of named theme icons. + + * gtk/gtkiconfactory.c: Rework sources so that the source + is *either* a pixbuf, or a filename, or an icon name, + instead of the pixbuf/filename mix it was before. Put a + workaround for get_pixbuf() so that it can return the + filename pixbuf, e.g, for render_icon(). + + * gtk/gtkiconfactory.c: Make the default setup use + themed icons, and add builtin icons to the default + icon theme for all the standard pixbufs, so we + don't rely on actually having an icon theme on disk. + + * gtk/gtkrc.c: Add support for @"icon-name" to specify + a themed icon for a stock icon source. + + * tests/Makefile.am test/testicontheme.c: Add a test + program from gnome-desktop. + + * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add + Net/IconThemeName / gtk-icon-theme-name setting. + + * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually + update the icon cache serial so we don't continually + think we are out-of-date. + + * gtk/gtkwidget.c: Fix a couple of references in doc comments + to ::direction_set that should have been to ::direction-changed + Wed Jul 2 14:45:41 2003 Owen Taylor * gtk/gtktoolbar.c (gtk_toolbar_realize): Attach the diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 48fd950b12..4f48f42119 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor + + * gtk/gtkicontheme.[ch]: Implement a loader for + named themed icon based on from gnome-desktop library + by Alex Larsson. + + * gtk/gtkiconthemeparser.[ch]: .ini file parsing code + from gnome-desktop. + + * gtk/gtkiconfactory.[ch]: Add + gtk_icon_source_set/get_icon_name() to allow stock icons + to be based off of named theme icons. + + * gtk/gtkiconfactory.c: Rework sources so that the source + is *either* a pixbuf, or a filename, or an icon name, + instead of the pixbuf/filename mix it was before. Put a + workaround for get_pixbuf() so that it can return the + filename pixbuf, e.g, for render_icon(). + + * gtk/gtkiconfactory.c: Make the default setup use + themed icons, and add builtin icons to the default + icon theme for all the standard pixbufs, so we + don't rely on actually having an icon theme on disk. + + * gtk/gtkrc.c: Add support for @"icon-name" to specify + a themed icon for a stock icon source. + + * tests/Makefile.am test/testicontheme.c: Add a test + program from gnome-desktop. + + * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add + Net/IconThemeName / gtk-icon-theme-name setting. + + * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually + update the icon cache serial so we don't continually + think we are out-of-date. + + * gtk/gtkwidget.c: Fix a couple of references in doc comments + to ::direction_set that should have been to ::direction-changed + Wed Jul 2 14:45:41 2003 Owen Taylor * gtk/gtktoolbar.c (gtk_toolbar_realize): Attach the diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 48fd950b12..4f48f42119 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor + + * gtk/gtkicontheme.[ch]: Implement a loader for + named themed icon based on from gnome-desktop library + by Alex Larsson. + + * gtk/gtkiconthemeparser.[ch]: .ini file parsing code + from gnome-desktop. + + * gtk/gtkiconfactory.[ch]: Add + gtk_icon_source_set/get_icon_name() to allow stock icons + to be based off of named theme icons. + + * gtk/gtkiconfactory.c: Rework sources so that the source + is *either* a pixbuf, or a filename, or an icon name, + instead of the pixbuf/filename mix it was before. Put a + workaround for get_pixbuf() so that it can return the + filename pixbuf, e.g, for render_icon(). + + * gtk/gtkiconfactory.c: Make the default setup use + themed icons, and add builtin icons to the default + icon theme for all the standard pixbufs, so we + don't rely on actually having an icon theme on disk. + + * gtk/gtkrc.c: Add support for @"icon-name" to specify + a themed icon for a stock icon source. + + * tests/Makefile.am test/testicontheme.c: Add a test + program from gnome-desktop. + + * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add + Net/IconThemeName / gtk-icon-theme-name setting. + + * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually + update the icon cache serial so we don't continually + think we are out-of-date. + + * gtk/gtkwidget.c: Fix a couple of references in doc comments + to ::direction_set that should have been to ::direction-changed + Wed Jul 2 14:45:41 2003 Owen Taylor * gtk/gtktoolbar.c (gtk_toolbar_realize): Attach the diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 48fd950b12..4f48f42119 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor + + * gtk/gtkicontheme.[ch]: Implement a loader for + named themed icon based on from gnome-desktop library + by Alex Larsson. + + * gtk/gtkiconthemeparser.[ch]: .ini file parsing code + from gnome-desktop. + + * gtk/gtkiconfactory.[ch]: Add + gtk_icon_source_set/get_icon_name() to allow stock icons + to be based off of named theme icons. + + * gtk/gtkiconfactory.c: Rework sources so that the source + is *either* a pixbuf, or a filename, or an icon name, + instead of the pixbuf/filename mix it was before. Put a + workaround for get_pixbuf() so that it can return the + filename pixbuf, e.g, for render_icon(). + + * gtk/gtkiconfactory.c: Make the default setup use + themed icons, and add builtin icons to the default + icon theme for all the standard pixbufs, so we + don't rely on actually having an icon theme on disk. + + * gtk/gtkrc.c: Add support for @"icon-name" to specify + a themed icon for a stock icon source. + + * tests/Makefile.am test/testicontheme.c: Add a test + program from gnome-desktop. + + * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add + Net/IconThemeName / gtk-icon-theme-name setting. + + * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually + update the icon cache serial so we don't continually + think we are out-of-date. + + * gtk/gtkwidget.c: Fix a couple of references in doc comments + to ::direction_set that should have been to ::direction-changed + Wed Jul 2 14:45:41 2003 Owen Taylor * gtk/gtktoolbar.c (gtk_toolbar_realize): Attach the diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 48fd950b12..4f48f42119 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor + + * gtk/gtkicontheme.[ch]: Implement a loader for + named themed icon based on from gnome-desktop library + by Alex Larsson. + + * gtk/gtkiconthemeparser.[ch]: .ini file parsing code + from gnome-desktop. + + * gtk/gtkiconfactory.[ch]: Add + gtk_icon_source_set/get_icon_name() to allow stock icons + to be based off of named theme icons. + + * gtk/gtkiconfactory.c: Rework sources so that the source + is *either* a pixbuf, or a filename, or an icon name, + instead of the pixbuf/filename mix it was before. Put a + workaround for get_pixbuf() so that it can return the + filename pixbuf, e.g, for render_icon(). + + * gtk/gtkiconfactory.c: Make the default setup use + themed icons, and add builtin icons to the default + icon theme for all the standard pixbufs, so we + don't rely on actually having an icon theme on disk. + + * gtk/gtkrc.c: Add support for @"icon-name" to specify + a themed icon for a stock icon source. + + * tests/Makefile.am test/testicontheme.c: Add a test + program from gnome-desktop. + + * gdk/x11/gdkevents-x11.c gtk/gtksettings.c: Add + Net/IconThemeName / gtk-icon-theme-name setting. + + * gtk/gtkiconfactory.c (ensure_cache_up_to_date): Actually + update the icon cache serial so we don't continually + think we are out-of-date. + + * gtk/gtkwidget.c: Fix a couple of references in doc comments + to ::direction_set that should have been to ::direction-changed + Wed Jul 2 14:45:41 2003 Owen Taylor * gtk/gtktoolbar.c (gtk_toolbar_realize): Attach the diff --git a/gdk/x11/gdkevents-x11.c b/gdk/x11/gdkevents-x11.c index 4da85c7560..7bb9d63d7c 100644 --- a/gdk/x11/gdkevents-x11.c +++ b/gdk/x11/gdkevents-x11.c @@ -2484,7 +2484,8 @@ static struct { "Gtk/IMStatusStyle", "gtk-im-status-style" }, { "Net/CursorBlink", "gtk-cursor-blink" }, { "Net/CursorBlinkTime", "gtk-cursor-blink-time" }, - { "Net/ThemeName", "gtk-theme-name" } + { "Net/ThemeName", "gtk-theme-name" }, + { "Net/IconThemeName", "gtk-icon-theme-name" }, }; static void diff --git a/gtk/Makefile.am b/gtk/Makefile.am index b9d616d0c3..3d0fc73cc5 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -5,6 +5,7 @@ SUBDIRS=stock-icons theme-bits INCLUDES = \ -DG_LOG_DOMAIN=\"Gtk\" \ -DGTK_LIBDIR=\"$(libdir)\" \ + -DGTK_DATADIR=\"$(datadir)\" \ -DGTK_DATA_PREFIX=\"$(prefix)\" \ -DGTK_SYSCONFDIR=\"$(sysconfdir)\" \ -DGTK_VERSION=\"$(GTK_VERSION)\" \ @@ -142,6 +143,7 @@ gtk_public_h_sources = \ gtkhscrollbar.h \ gtkhseparator.h \ gtkiconfactory.h \ + gtkicontheme.h \ gtkimage.h \ gtkimagemenuitem.h \ gtkimcontext.h \ @@ -317,6 +319,9 @@ gtk_c_sources = \ gtkhsv.c \ gtkhsv.h \ gtkiconfactory.c \ + gtkicontheme.c \ + gtkiconthemeparser.c \ + gtkiconthemeparser.h \ gtkimage.c \ gtkimagemenuitem.c \ gtkimcontext.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index 2c7ffe5d72..5115dbe4e4 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -82,6 +82,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkiconfactory.c b/gtk/gtkiconfactory.c index 847636b79f..25d77bf633 100644 --- a/gtk/gtkiconfactory.c +++ b/gtk/gtkiconfactory.c @@ -31,20 +31,32 @@ #include "gtkiconfactory.h" #include "stock-icons/gtkstockpixbufs.h" #include "gtkdebug.h" +#include "gtkicontheme.h" #include "gtksettings.h" #include "gtkstock.h" +#include "gtkwidget.h" #include "gtkintl.h" static GSList *all_icon_factories = NULL; +typedef enum { + GTK_ICON_SOURCE_EMPTY, + GTK_ICON_SOURCE_ICON_NAME, + GTK_ICON_SOURCE_FILENAME, + GTK_ICON_SOURCE_PIXBUF +} GtkIconSourceType; + struct _GtkIconSource { - /* Either filename or pixbuf can be NULL. If both are non-NULL, - * the pixbuf is assumed to be the already-loaded contents of the - * file. - */ - gchar *filename; - GdkPixbuf *pixbuf; + GtkIconSourceType type; + + union { + gchar *icon_name; + gchar *filename; + GdkPixbuf *pixbuf; + } source; + + GdkPixbuf *filename_pixbuf; GtkTextDirection direction; GtkStateType state; @@ -65,11 +77,17 @@ static void gtk_icon_factory_init (GtkIconFactory *icon_factory); static void gtk_icon_factory_class_init (GtkIconFactoryClass *klass); static void gtk_icon_factory_finalize (GObject *object); static void get_default_icons (GtkIconFactory *icon_factory); +static void icon_source_clear (GtkIconSource *source); static GtkIconSize icon_size_register_intern (const gchar *name, gint width, gint height); +#define GTK_ICON_SOURCE_INIT(any_direction, any_state, any_size) \ + { GTK_ICON_SOURCE_EMPTY, { NULL }, NULL, \ + 0, 0, 0, \ + any_direction, any_state, any_size } + GType gtk_icon_factory_get_type (void) { @@ -326,157 +344,105 @@ gtk_icon_factory_lookup_default (const gchar *stock_id) } static void -add_source (GtkIconSet *set, - GtkIconSource *source, - const gchar *inline_data) +register_stock_icon (GtkIconFactory *factory, + const gchar *stock_id) { - source->pixbuf = gdk_pixbuf_new_from_inline (-1, inline_data, FALSE, NULL); - g_assert (source->pixbuf); + GtkIconSet *set = gtk_icon_set_new (); + GtkIconSource source = GTK_ICON_SOURCE_INIT (TRUE, TRUE, TRUE); - gtk_icon_set_add_source (set, source); - - g_object_unref (source->pixbuf); + source.type = GTK_ICON_SOURCE_ICON_NAME; + source.source.icon_name = (gchar *)stock_id; + gtk_icon_set_add_source (set, &source); + + gtk_icon_factory_add (factory, stock_id, set); + gtk_icon_set_unref (set); } -#if 0 -static GtkIconSet * -sized_icon_set_from_inline (const guchar *inline_data, - GtkIconSize size) +static void +register_bidi_stock_icon (GtkIconFactory *factory, + const gchar *stock_id, + const gchar *stock_id_ltr, + const gchar *stock_id_rtl) { - GtkIconSet *set; + GtkIconSet *set = gtk_icon_set_new (); + GtkIconSource source = GTK_ICON_SOURCE_INIT (FALSE, TRUE, TRUE); - GtkIconSource source = { NULL, NULL, 0, 0, 0, - TRUE, TRUE, FALSE }; - - source.size = size; - - set = gtk_icon_set_new (); - - add_source (set, source, inline_data); - - return set; -} -#endif - -static GtkIconSet * -sized_with_fallback_icon_set_from_inline (const guchar *fallback_data_ltr, - const guchar *fallback_data_rtl, - const guchar *inline_data_ltr, - const guchar *inline_data_rtl, - GtkIconSize size) -{ - GtkIconSet *set; - - GtkIconSource source = { NULL, NULL, 0, 0, 0, - TRUE, TRUE, FALSE }; - - set = gtk_icon_set_new (); - - source.size = size; - source.any_direction = inline_data_rtl == NULL; - + source.type = GTK_ICON_SOURCE_ICON_NAME; + source.source.icon_name = (gchar *)stock_id_ltr; source.direction = GTK_TEXT_DIR_LTR; - add_source (set, &source, inline_data_ltr); + gtk_icon_set_add_source (set, &source); - if (inline_data_rtl != NULL) - { - source.direction = GTK_TEXT_DIR_RTL; - add_source (set, &source, inline_data_rtl); - } - - source.any_size = TRUE; - source.any_direction = fallback_data_rtl == NULL; - - source.direction = GTK_TEXT_DIR_LTR; - add_source (set, &source, fallback_data_ltr); - - if (fallback_data_rtl != NULL) - { - source.direction = GTK_TEXT_DIR_RTL; - add_source (set, &source, fallback_data_rtl); - } - - return set; -} - -static GtkIconSet * -unsized_icon_set_from_inline (const guchar *inline_data) -{ - GtkIconSet *set; - - /* This icon can be used for any direction/state/size */ - GtkIconSource source = { NULL, NULL, 0, 0, 0, - TRUE, TRUE, TRUE }; - - set = gtk_icon_set_new (); - - add_source (set, &source, inline_data); - - return set; -} - -#if 0 -static void -add_sized (GtkIconFactory *factory, - const guchar *inline_data, - GtkIconSize size, - const gchar *stock_id) -{ - GtkIconSet *set; - - set = sized_icon_set_from_inline (inline_data, size); + source.type = GTK_ICON_SOURCE_ICON_NAME; + source.source.icon_name = (gchar *)stock_id_rtl; + source.direction = GTK_TEXT_DIR_RTL; + gtk_icon_set_add_source (set, &source); gtk_icon_factory_add (factory, stock_id, set); - - gtk_icon_set_unref (set); -} -#endif - -static void -add_sized_with_fallback_and_rtl (GtkIconFactory *factory, - const guchar *fallback_data_ltr, - const guchar *fallback_data_rtl, - const guchar *inline_data_ltr, - const guchar *inline_data_rtl, - GtkIconSize size, - const gchar *stock_id) -{ - GtkIconSet *set; - - set = sized_with_fallback_icon_set_from_inline (fallback_data_ltr, fallback_data_rtl, - inline_data_ltr, inline_data_rtl, - size); - - gtk_icon_factory_add (factory, stock_id, set); - gtk_icon_set_unref (set); } static void -add_sized_with_fallback (GtkIconFactory *factory, - const guchar *fallback_data, - const guchar *inline_data, - GtkIconSize size, - const gchar *stock_id) +add_default_image (const gchar *stock_id, + gint size, + const guchar *inline_data) { - add_sized_with_fallback_and_rtl (factory, - fallback_data, NULL, - inline_data, NULL, - size, stock_id); + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_inline (-1, inline_data, FALSE, NULL); + g_assert (pixbuf); + + gtk_icon_theme_add_builtin_icon (stock_id, size, pixbuf); + + g_object_unref (pixbuf); } static void -add_unsized (GtkIconFactory *factory, - const guchar *inline_data, - const gchar *stock_id) +add_icon (GtkIconFactory *factory, + const gchar *stock_id, + gint size, + const guchar *inline_data) { - GtkIconSet *set; - - set = unsized_icon_set_from_inline (inline_data); - - gtk_icon_factory_add (factory, stock_id, set); + register_stock_icon (factory, stock_id); - gtk_icon_set_unref (set); + add_default_image (stock_id, size, inline_data); +} + +static void +add_icon2 (GtkIconFactory *factory, + const gchar *stock_id, + gint size1, + const guchar *inline_data1, + gint size2, + const guchar *inline_data2) +{ + register_stock_icon (factory, stock_id); + + add_default_image (stock_id, size1, inline_data1); + add_default_image (stock_id, size2, inline_data2); +} + +static void +add_icon_bidi2 (GtkIconFactory *factory, + const gchar *stock_id, + gint size1, + const guchar *inline_data_ltr1, + const guchar *inline_data_rtl1, + gint size2, + const guchar *inline_data_ltr2, + const guchar *inline_data_rtl2) +{ + gchar *stock_id_ltr = g_strconcat (stock_id, "-ltr", NULL); + gchar *stock_id_rtl = g_strconcat (stock_id, "-rtl", NULL); + + register_bidi_stock_icon (factory, stock_id, + stock_id_ltr, stock_id_rtl); + + add_default_image (stock_id_ltr, size1, inline_data_ltr1); + add_default_image (stock_id_ltr, size2, inline_data_ltr2); + + add_default_image (stock_id_rtl, size1, inline_data_rtl1); + add_default_image (stock_id_rtl, size2, inline_data_rtl2); + + g_free (stock_id_ltr); + g_free (stock_id_rtl); } static void @@ -484,369 +450,243 @@ get_default_icons (GtkIconFactory *factory) { /* KEEP IN SYNC with gtkstock.c */ - /* We add all stock icons unsized, since it's confusing if icons only - * can be loaded at certain sizes. - */ - /* Have dialog size */ - add_unsized (factory, stock_dialog_error_48, GTK_STOCK_DIALOG_ERROR); - add_unsized (factory, stock_dialog_info_48, GTK_STOCK_DIALOG_INFO); - add_unsized (factory, stock_dialog_question_48, GTK_STOCK_DIALOG_QUESTION); - add_unsized (factory, stock_dialog_warning_48,GTK_STOCK_DIALOG_WARNING); + add_icon (factory, GTK_STOCK_DIALOG_ERROR, 48, stock_dialog_error_48); + add_icon (factory, GTK_STOCK_DIALOG_INFO, 48, stock_dialog_info_48); + add_icon (factory, GTK_STOCK_DIALOG_QUESTION, 48, stock_dialog_question_48); + add_icon (factory, GTK_STOCK_DIALOG_WARNING, 48, stock_dialog_warning_48); /* Have dnd size */ - add_unsized (factory, stock_dnd_32, GTK_STOCK_DND); - add_unsized (factory, stock_dnd_multiple_32, GTK_STOCK_DND_MULTIPLE); + add_icon (factory, GTK_STOCK_DND, 32, stock_dnd_32); + add_icon (factory, GTK_STOCK_DND_MULTIPLE, 32, stock_dnd_multiple_32); /* Have button sizes */ - add_unsized (factory, stock_apply_20, GTK_STOCK_APPLY); - add_unsized (factory, stock_cancel_20, GTK_STOCK_CANCEL); - add_unsized (factory, stock_no_20, GTK_STOCK_NO); - add_unsized (factory, stock_ok_20, GTK_STOCK_OK); - add_unsized (factory, stock_yes_20, GTK_STOCK_YES); + add_icon (factory, GTK_STOCK_APPLY, 20, stock_apply_20); + add_icon (factory, GTK_STOCK_CANCEL, 20, stock_cancel_20); + add_icon (factory, GTK_STOCK_NO, 20, stock_no_20); + add_icon (factory, GTK_STOCK_OK, 20, stock_ok_20); + add_icon (factory, GTK_STOCK_YES, 20, stock_yes_20); /* Generic + button sizes */ - add_sized_with_fallback (factory, - stock_close_24, - stock_close_20, - GTK_ICON_SIZE_BUTTON, - GTK_STOCK_CLOSE); + add_icon2 (factory, GTK_STOCK_CLOSE, + 20, stock_close_20, + 24, stock_close_24); /* Generic + menu sizes */ - add_sized_with_fallback (factory, - stock_add_24, - stock_add_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_ADD); + add_icon2 (factory, GTK_STOCK_ADD, + 16, stock_add_16, + 24, stock_add_24); - add_sized_with_fallback (factory, - stock_align_center_24, - stock_align_center_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_CENTER); + add_icon2 (factory, GTK_STOCK_JUSTIFY_CENTER, + 16, stock_align_center_16, + 24, stock_align_center_24); - add_sized_with_fallback (factory, - stock_align_justify_24, - stock_align_justify_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_FILL); + add_icon2 (factory, GTK_STOCK_JUSTIFY_FILL, + 16, stock_align_justify_16, + 24, stock_align_justify_24); - add_sized_with_fallback (factory, - stock_align_left_24, - stock_align_left_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_LEFT); + add_icon2 (factory, GTK_STOCK_JUSTIFY_LEFT, + 16, stock_align_left_16, + 24, stock_align_left_24); + + add_icon2 (factory, GTK_STOCK_JUSTIFY_RIGHT, + 16, stock_align_right_16, + 24, stock_align_right_24); - add_sized_with_fallback (factory, - stock_align_right_24, - stock_align_right_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_RIGHT); + add_icon2 (factory, GTK_STOCK_GOTO_BOTTOM, + 16, stock_bottom_16, + 24, stock_bottom_24); + + add_icon2 (factory, GTK_STOCK_CDROM, + 16, stock_cdrom_16, + 24, stock_cdrom_24); - add_sized_with_fallback (factory, - stock_bottom_24, - stock_bottom_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_BOTTOM); + add_icon2 (factory, GTK_STOCK_CONVERT, + 16, stock_convert_16, + 24, stock_convert_24); - add_sized_with_fallback (factory, - stock_cdrom_24, - stock_cdrom_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CDROM); + add_icon2 (factory, GTK_STOCK_COPY, + 16, stock_copy_16, + 24, stock_copy_24); - add_sized_with_fallback (factory, - stock_convert_24, - stock_convert_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CONVERT); + add_icon2 (factory, GTK_STOCK_CUT, + 16, stock_cut_16, + 24, stock_cut_24); - add_sized_with_fallback (factory, - stock_copy_24, - stock_copy_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_COPY); + add_icon2 (factory, GTK_STOCK_GO_DOWN, + 16, stock_down_arrow_16, + 24, stock_down_arrow_24); - add_sized_with_fallback (factory, - stock_cut_24, - stock_cut_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CUT); + add_icon2 (factory, GTK_STOCK_EXECUTE, + 16, stock_exec_16, + 24, stock_exec_24); - add_sized_with_fallback (factory, - stock_down_arrow_24, - stock_down_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_DOWN); + add_icon2 (factory, GTK_STOCK_QUIT, + 16, stock_exit_16, + 24, stock_exit_24); - add_sized_with_fallback (factory, - stock_exec_24, - stock_exec_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_EXECUTE); + add_icon_bidi2 (factory, GTK_STOCK_GOTO_FIRST, + 16, stock_first_16, stock_last_16, + 24, stock_first_24, stock_last_24); - add_sized_with_fallback (factory, - stock_exit_24, - stock_exit_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_QUIT); + add_icon2 (factory, GTK_STOCK_SELECT_FONT, + 16, stock_font_16, + 24, stock_font_24); - add_sized_with_fallback_and_rtl (factory, - stock_first_24, - stock_last_24, - stock_first_16, - stock_last_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_FIRST); + add_icon2 (factory, GTK_STOCK_HELP, + 16, stock_help_16, + 24, stock_help_24); - add_sized_with_fallback (factory, - stock_font_24, - stock_font_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SELECT_FONT); + add_icon2 (factory, GTK_STOCK_HOME, + 16, stock_home_16, + 24, stock_home_24); - add_sized_with_fallback (factory, - stock_help_24, - stock_help_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_HELP); + add_icon_bidi2 (factory, GTK_STOCK_JUMP_TO, + 16, stock_jump_to_16, stock_jump_to_rtl_16, + 24, stock_jump_to_24, stock_jump_to_rtl_24); - add_sized_with_fallback (factory, - stock_home_24, - stock_home_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_HOME); + add_icon_bidi2 (factory, GTK_STOCK_GOTO_LAST, + 16, stock_last_16, stock_first_16, + 24, stock_last_24, stock_first_24); - add_sized_with_fallback_and_rtl (factory, - stock_jump_to_24, - stock_jump_to_rtl_24, - stock_jump_to_16, - stock_jump_to_rtl_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUMP_TO); + add_icon_bidi2 (factory, GTK_STOCK_GO_BACK, + 16, stock_left_arrow_16, stock_right_arrow_16, + 24, stock_left_arrow_24, stock_right_arrow_24); - add_sized_with_fallback_and_rtl (factory, - stock_last_24, - stock_first_24, - stock_last_16, - stock_first_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_LAST); + add_icon2 (factory, GTK_STOCK_MISSING_IMAGE, + 16, stock_missing_image_16, + 24, stock_missing_image_24); - add_sized_with_fallback_and_rtl (factory, - stock_left_arrow_24, - stock_right_arrow_24, - stock_left_arrow_16, - stock_right_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_BACK); + add_icon2 (factory, GTK_STOCK_NEW, + 16, stock_new_16, + 24, stock_new_24); - add_sized_with_fallback (factory, - stock_missing_image_24, - stock_missing_image_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_MISSING_IMAGE); + add_icon2 (factory, GTK_STOCK_OPEN, + 16, stock_open_16, + 24, stock_open_24); - add_sized_with_fallback (factory, - stock_new_24, - stock_new_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_NEW); + add_icon2 (factory, GTK_STOCK_PASTE, + 16, stock_paste_16, + 24, stock_paste_24); - add_sized_with_fallback (factory, - stock_open_24, - stock_open_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_OPEN); + add_icon2 (factory, GTK_STOCK_PREFERENCES, + 16, stock_preferences_16, + 24, stock_preferences_24); - add_sized_with_fallback (factory, - stock_paste_24, - stock_paste_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PASTE); + add_icon2 (factory, GTK_STOCK_PRINT, + 16, stock_print_16, + 24, stock_print_24); - add_sized_with_fallback (factory, - stock_preferences_24, - stock_preferences_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PREFERENCES); + add_icon2 (factory, GTK_STOCK_PRINT_PREVIEW, + 16, stock_print_preview_16, + 24, stock_print_preview_24); - add_sized_with_fallback (factory, - stock_print_24, - stock_print_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PRINT); - - add_sized_with_fallback (factory, - stock_print_preview_24, - stock_print_preview_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PRINT_PREVIEW); - - add_sized_with_fallback (factory, - stock_properties_24, - stock_properties_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PROPERTIES); + add_icon2 (factory, GTK_STOCK_PROPERTIES, + 16, stock_properties_16, + 24, stock_properties_24); - add_sized_with_fallback_and_rtl (factory, - stock_redo_24, - stock_redo_rtl_24, - stock_redo_16, - stock_redo_rtl_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REDO); + add_icon_bidi2 (factory, GTK_STOCK_REDO, + 16, stock_redo_16, stock_redo_rtl_16, + 24, stock_redo_24, stock_redo_rtl_24); - add_sized_with_fallback (factory, - stock_remove_24, - stock_remove_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REMOVE); + add_icon2 (factory, GTK_STOCK_REMOVE, + 16, stock_remove_16, + 24, stock_remove_24); - add_sized_with_fallback (factory, - stock_refresh_24, - stock_refresh_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REFRESH); + add_icon2 (factory, GTK_STOCK_REFRESH, + 16, stock_refresh_16, + 24, stock_refresh_24); - add_sized_with_fallback_and_rtl (factory, - stock_revert_24, - stock_revert_rtl_24, - stock_revert_16, - stock_revert_rtl_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REVERT_TO_SAVED); + add_icon_bidi2 (factory, GTK_STOCK_REVERT_TO_SAVED, + 16, stock_revert_16, stock_revert_rtl_16, + 24, stock_revert_24, stock_revert_rtl_24); - add_sized_with_fallback_and_rtl (factory, - stock_right_arrow_24, - stock_left_arrow_24, - stock_right_arrow_16, - stock_left_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_FORWARD); + add_icon_bidi2 (factory, GTK_STOCK_GO_FORWARD, + 16, stock_right_arrow_16, stock_left_arrow_16, + 24, stock_right_arrow_24, stock_left_arrow_24); - add_sized_with_fallback (factory, - stock_save_24, - stock_save_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SAVE); + add_icon2 (factory, GTK_STOCK_SAVE, + 16, stock_save_16, + 24, stock_save_24); - add_sized_with_fallback (factory, - stock_save_24, - stock_save_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FLOPPY); + add_icon2 (factory, GTK_STOCK_FLOPPY, + 16, stock_save_16, + 24, stock_save_24); - add_sized_with_fallback (factory, - stock_save_as_24, - stock_save_as_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SAVE_AS); + add_icon2 (factory, GTK_STOCK_SAVE_AS, + 16, stock_save_as_16, + 24, stock_save_as_24); - add_sized_with_fallback (factory, - stock_search_24, - stock_search_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FIND); + add_icon2 (factory, GTK_STOCK_FIND, + 16, stock_search_16, + 24, stock_search_24); - add_sized_with_fallback (factory, - stock_search_replace_24, - stock_search_replace_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FIND_AND_REPLACE); + add_icon2 (factory, GTK_STOCK_FIND_AND_REPLACE, + 16, stock_search_replace_16, + 24, stock_search_replace_24); - add_sized_with_fallback (factory, - stock_sort_descending_24, - stock_sort_descending_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SORT_DESCENDING); + add_icon2 (factory, GTK_STOCK_SORT_DESCENDING, + 16, stock_sort_descending_16, + 24, stock_sort_descending_24); - add_sized_with_fallback (factory, - stock_sort_ascending_24, - stock_sort_ascending_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SORT_ASCENDING); + add_icon2 (factory, GTK_STOCK_SORT_ASCENDING, + 16, stock_sort_ascending_16, + 24, stock_sort_ascending_24); - add_sized_with_fallback (factory, - stock_spellcheck_24, - stock_spellcheck_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SPELL_CHECK); + add_icon2 (factory, GTK_STOCK_SPELL_CHECK, + 16, stock_spellcheck_16, + 24, stock_spellcheck_24); - add_sized_with_fallback (factory, - stock_stop_24, - stock_stop_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_STOP); + add_icon2 (factory, GTK_STOCK_STOP, + 16, stock_stop_16, + 24, stock_stop_24); - add_sized_with_fallback (factory, - stock_text_bold_24, - stock_text_bold_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_BOLD); + add_icon2 (factory, GTK_STOCK_BOLD, + 16, stock_text_bold_16, + 24, stock_text_bold_24); - add_sized_with_fallback (factory, - stock_text_italic_24, - stock_text_italic_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_ITALIC); + add_icon2 (factory, GTK_STOCK_ITALIC, + 16, stock_text_italic_16, + 24, stock_text_italic_24); - add_sized_with_fallback (factory, - stock_text_strikethrough_24, - stock_text_strikethrough_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_STRIKETHROUGH); + add_icon2 (factory, GTK_STOCK_STRIKETHROUGH, + 16, stock_text_strikethrough_16, + 24, stock_text_strikethrough_24); - add_sized_with_fallback (factory, - stock_text_underline_24, - stock_text_underline_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_UNDERLINE); + add_icon2 (factory, GTK_STOCK_UNDERLINE, + 16, stock_text_underline_16, + 24, stock_text_underline_24); - add_sized_with_fallback (factory, - stock_top_24, - stock_top_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_TOP); + add_icon2 (factory, GTK_STOCK_GOTO_TOP, + 16, stock_top_16, + 24, stock_top_24); - add_sized_with_fallback (factory, - stock_trash_24, - stock_trash_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_DELETE); + add_icon2 (factory, GTK_STOCK_DELETE, + 16, stock_trash_16, + 24, stock_trash_24); - add_sized_with_fallback_and_rtl (factory, - stock_undelete_24, - stock_undelete_rtl_24, - stock_undelete_16, - stock_undelete_rtl_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_UNDELETE); + add_icon_bidi2 (factory, GTK_STOCK_UNDELETE, + 16, stock_undelete_16, stock_undelete_rtl_16, + 24, stock_undelete_24, stock_undelete_rtl_24); - add_sized_with_fallback_and_rtl (factory, - stock_undo_24, - stock_undo_rtl_24, - stock_undo_16, - stock_undo_rtl_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_UNDO); + add_icon_bidi2 (factory, GTK_STOCK_UNDO, + 16, stock_undo_16, stock_undo_rtl_16, + 24, stock_undo_24, stock_undo_rtl_24); - add_sized_with_fallback (factory, - stock_up_arrow_24, - stock_up_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_UP); + add_icon2 (factory, GTK_STOCK_GO_UP, + 16, stock_up_arrow_16, + 24, stock_up_arrow_24); -/* Generic size only */ + /* Generic size only */ - add_unsized (factory, stock_clear_24, GTK_STOCK_CLEAR); - add_unsized (factory, stock_colorselector_24, GTK_STOCK_SELECT_COLOR); - add_unsized (factory, stock_color_picker_25, GTK_STOCK_COLOR_PICKER); - add_unsized (factory, stock_index_24, GTK_STOCK_INDEX); - add_unsized (factory, stock_zoom_1_24, GTK_STOCK_ZOOM_100); - add_unsized (factory, stock_zoom_fit_24, GTK_STOCK_ZOOM_FIT); - add_unsized (factory, stock_zoom_in_24, GTK_STOCK_ZOOM_IN); - add_unsized (factory, stock_zoom_out_24, GTK_STOCK_ZOOM_OUT); + add_icon (factory, GTK_STOCK_CLEAR, 24, stock_clear_24); + add_icon (factory, GTK_STOCK_SELECT_COLOR, 24, stock_colorselector_24); + add_icon (factory, GTK_STOCK_COLOR_PICKER, 25, stock_color_picker_25); + add_icon (factory, GTK_STOCK_INDEX, 24, stock_index_24); + add_icon (factory, GTK_STOCK_ZOOM_100, 24, stock_zoom_1_24); + add_icon (factory, GTK_STOCK_ZOOM_FIT, 24, stock_zoom_fit_24); + add_icon (factory, GTK_STOCK_ZOOM_IN, 24, stock_zoom_in_24); + add_icon (factory, GTK_STOCK_ZOOM_OUT, 24, stock_zoom_out_24); } /************************************************************ @@ -1423,10 +1263,6 @@ gtk_icon_size_get_name (GtkIconSize size) /* Icon Set */ -/* Clear icon set contents, drop references to all contained - * GdkPixbuf objects and forget all GtkIconSources. Used to - * recycle an icon set. - */ static GdkPixbuf *find_in_cache (GtkIconSet *icon_set, GtkStyle *style, GtkTextDirection direction, @@ -1438,6 +1274,10 @@ static void add_to_cache (GtkIconSet *icon_set, GtkStateType state, GtkIconSize size, GdkPixbuf *pixbuf); +/* Clear icon set contents, drop references to all contained + * GdkPixbuf objects and forget all GtkIconSources. Used to + * recycle an icon set. + */ static void clear_cache (GtkIconSet *icon_set, gboolean style_detach); static GSList* copy_cache (GtkIconSet *icon_set, @@ -1512,16 +1352,15 @@ gtk_icon_set_new_from_pixbuf (GdkPixbuf *pixbuf) { GtkIconSet *set; - GtkIconSource source = { NULL, NULL, 0, 0, 0, - TRUE, TRUE, TRUE }; + GtkIconSource source = GTK_ICON_SOURCE_INIT (TRUE, TRUE, TRUE); g_return_val_if_fail (pixbuf != NULL, NULL); set = gtk_icon_set_new (); - source.pixbuf = pixbuf; - + gtk_icon_source_set_pixbuf (&source, pixbuf); gtk_icon_set_add_source (set, &source); + gtk_icon_source_set_pixbuf (&source, NULL); return set; } @@ -1648,16 +1487,16 @@ sizes_equivalent (GtkIconSize lhs, #endif } -static GtkIconSource* -find_and_prep_icon_source (GtkIconSet *icon_set, - GtkTextDirection direction, - GtkStateType state, - GtkIconSize size) +static GtkIconSource * +find_best_matching_source (GtkIconSet *icon_set, + GtkTextDirection direction, + GtkStateType state, + GtkIconSize size, + GSList *failed) { GtkIconSource *source; GSList *tmp_list; - - + /* We need to find the best icon source. Direction matters more * than state, state matters more than size. icon_set->sources * is sorted according to wildness, so if we take the first @@ -1676,47 +1515,175 @@ find_and_prep_icon_source (GtkIconSet *icon_set, (s->any_state || (s->state == state)) && (s->any_size || (sizes_equivalent (size, s->size)))) { - source = s; - break; - } - + if (!g_slist_find (failed, s)) + { + source = s; + break; + } + } + tmp_list = g_slist_next (tmp_list); } - if (source == NULL) - return NULL; + return source; +} - if (source->pixbuf == NULL) +static gboolean +ensure_filename_pixbuf (GtkIconSet *icon_set, + GtkIconSource *source) +{ + if (source->filename_pixbuf == NULL) { GError *error = NULL; - g_assert (source->filename); - source->pixbuf = gdk_pixbuf_new_from_file (source->filename, &error); + source->filename_pixbuf = gdk_pixbuf_new_from_file (source->source.filename, &error); + + if (source->filename_pixbuf == NULL) + { + /* Remove this icon source so we don't keep trying to + * load it. + */ + g_warning (_("Error loading icon: %s"), error->message); + g_error_free (error); + + icon_set->sources = g_slist_remove (icon_set->sources, source); + + gtk_icon_source_free (source); - if (source->pixbuf == NULL) - { - /* Remove this icon source so we don't keep trying to - * load it. - */ - g_warning (_("Error loading icon: %s"), error->message); - g_error_free (error); - - icon_set->sources = g_slist_remove (icon_set->sources, source); - - gtk_icon_source_free (source); - - /* Try to fall back to other sources */ - if (icon_set->sources != NULL) - return find_and_prep_icon_source (icon_set, - direction, - state, - size); - else - return NULL; - } + return FALSE; + } + } + + return TRUE; +} + +static GdkPixbuf * +render_icon_name_pixbuf (GtkIconSource *icon_source, + GtkStyle *style, + GtkTextDirection direction, + GtkStateType state, + GtkIconSize size, + GtkWidget *widget, + const char *detail) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *tmp_pixbuf; + GtkIconSource tmp_source; + GdkScreen *screen; + GtkIconTheme *icon_theme; + GtkSettings *settings; + gint width, height, pixel_size; + GError *error = NULL; + + if (widget && gtk_widget_has_screen (widget)) + screen = gtk_widget_get_screen (widget); + else if (style->colormap) + screen = gdk_colormap_get_screen (style->colormap); + else + { + screen = gdk_screen_get_default (); + GTK_NOTE (MULTIHEAD, + g_warning ("Using the default screen for gtk_icon_source_render_icon()")); } - return source; + icon_theme = gtk_icon_theme_get_for_screen (screen); + settings = gtk_settings_get_for_screen (screen); + + if (!gtk_icon_size_lookup_for_settings (settings, size, &width, &height)) + { + g_warning ("Invalid icon size %d\n", size); + width = height = 24; + } + + pixel_size = MIN (width, height); + + tmp_pixbuf = gtk_icon_theme_load_icon (icon_theme, + icon_source->source.icon_name, + pixel_size, 0, + &error); + + if (!tmp_pixbuf) + { + g_warning ("Error loading theme icon for stock: %s", error->message); + return NULL; + } + + tmp_source = *icon_source; + tmp_source.type = GTK_ICON_SOURCE_PIXBUF; + tmp_source.source.pixbuf = tmp_pixbuf; + + pixbuf = gtk_style_render_icon (style, &tmp_source, + direction, state, -1, + widget, detail); + + if (!pixbuf) + g_warning ("Failed to render icon"); + + g_object_unref (tmp_pixbuf); + + return pixbuf; +} + +static GdkPixbuf * +find_and_render_icon_source (GtkIconSet *icon_set, + GtkStyle *style, + GtkTextDirection direction, + GtkStateType state, + GtkIconSize size, + GtkWidget *widget, + const char *detail) +{ + GSList *failed = NULL; + GdkPixbuf *pixbuf = NULL; + + /* We treat failure in two different ways: + * + * A) If loading a source that specifies a filename fails, + * we treat that as permanent, and remove the source + * from the GtkIconSet. (in ensure_filename_pixbuf () + * B) If loading a themed icon fails, or scaling an icon + * fails, we treat that as transient and will try + * again next time the icon falls out of the cache + * and we need to recreate it. + */ + while (pixbuf == NULL) + { + GtkIconSource *source = find_best_matching_source (icon_set, direction, state, size, failed); + + if (source == NULL) + break; + + switch (source->type) + { + case GTK_ICON_SOURCE_FILENAME: + if (!ensure_filename_pixbuf (icon_set, source)) + break; + /* Fall through */ + case GTK_ICON_SOURCE_PIXBUF: + pixbuf = gtk_style_render_icon (style, source, + direction, state, size, + widget, detail); + if (!pixbuf) + { + g_warning ("Failed to render icon"); + failed = g_slist_prepend (failed, source); + } + break; + case GTK_ICON_SOURCE_ICON_NAME: + pixbuf = render_icon_name_pixbuf (source, style, + direction, state, size, + widget, detail); + if (!pixbuf) + failed = g_slist_prepend (failed, source); + break; + case GTK_ICON_SOURCE_EMPTY: + g_assert_not_reached (); + } + } + + g_slist_free (failed); + + return pixbuf; } static GdkPixbuf* @@ -1728,10 +1695,14 @@ render_fallback_image (GtkStyle *style, const char *detail) { /* This icon can be used for any direction/state/size */ - static GtkIconSource fallback_source = { NULL, NULL, 0, 0, 0, TRUE, TRUE, TRUE }; + static GtkIconSource fallback_source = GTK_ICON_SOURCE_INIT (TRUE, TRUE, TRUE); - if (fallback_source.pixbuf == NULL) - fallback_source.pixbuf = gdk_pixbuf_new_from_inline (-1, stock_missing_image_24, FALSE, NULL); + if (fallback_source.type == GTK_ICON_SOURCE_EMPTY) + { + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_inline (-1, stock_missing_image_24, FALSE, NULL); + gtk_icon_source_set_pixbuf (&fallback_source, pixbuf); + g_object_unref (pixbuf); + } return gtk_style_render_icon (style, &fallback_source, @@ -1749,8 +1720,12 @@ render_fallback_image (GtkStyle *style, * @direction: text direction * @state: widget state * @size: icon size - * @widget: widget that will display the icon, or %NULL - * @detail: detail to pass to the theme engine, or %NULL + * @widget: widget that will display the icon, or %NULL. + * The only use that is typically made of this + * is to determine the appropriate #GdkScreen. + * @detail: detail to pass to the theme engine, or %NULL. + * Note that passing a detail of anything but %NULL + * will disable caching. * * Renders an icon using gtk_style_render_icon(). In most cases, * gtk_widget_render_icon() is better, since it automatically provides @@ -1771,46 +1746,34 @@ gtk_icon_set_render_icon (GtkIconSet *icon_set, const char *detail) { GdkPixbuf *icon; - GtkIconSource *source; g_return_val_if_fail (icon_set != NULL, NULL); g_return_val_if_fail (GTK_IS_STYLE (style), NULL); if (icon_set->sources == NULL) return render_fallback_image (style, direction, state, size, widget, detail); - - icon = find_in_cache (icon_set, style, direction, - state, size); - if (icon) + if (detail == NULL) { - g_object_ref (icon); - return icon; + icon = find_in_cache (icon_set, style, direction, + state, size); + + if (icon) + { + g_object_ref (icon); + return icon; + } } - - source = find_and_prep_icon_source (icon_set, direction, state, size); - if (source == NULL) - return render_fallback_image (style, direction, state, size, widget, detail); - - g_assert (source->pixbuf != NULL); - - icon = gtk_style_render_icon (style, - source, - direction, - state, - size, - widget, - detail); + icon = find_and_render_icon_source (icon_set, style, direction, state, size, + widget, detail); if (icon == NULL) - { - g_warning ("Theme engine failed to render icon"); - return NULL; - } - - add_to_cache (icon_set, style, direction, state, size, icon); + icon = render_fallback_image (style, direction, state, size, widget, detail); + + if (detail == NULL) + add_to_cache (icon_set, style, direction, state, size, icon); return icon; } @@ -1881,10 +1844,9 @@ gtk_icon_set_add_source (GtkIconSet *icon_set, g_return_if_fail (icon_set != NULL); g_return_if_fail (source != NULL); - if (source->pixbuf == NULL && - source->filename == NULL) + if (source->type == GTK_ICON_SOURCE_EMPTY) { - g_warning ("Useless GtkIconSource contains NULL filename and pixbuf"); + g_warning ("Useless empty GtkIconSource"); return; } @@ -2044,10 +2006,24 @@ gtk_icon_source_copy (const GtkIconSource *source) *copy = *source; - copy->filename = g_strdup (source->filename); - copy->size = source->size; - if (copy->pixbuf) - g_object_ref (copy->pixbuf); + switch (copy->type) + { + case GTK_ICON_SOURCE_EMPTY: + break; + case GTK_ICON_SOURCE_ICON_NAME: + copy->source.icon_name = g_strdup (copy->source.icon_name); + break; + case GTK_ICON_SOURCE_FILENAME: + copy->source.filename = g_strdup (copy->source.filename); + if (copy->filename_pixbuf) + g_object_ref (copy->filename_pixbuf); + break; + case GTK_ICON_SOURCE_PIXBUF: + g_object_ref (copy->source.pixbuf); + break; + default: + g_assert_not_reached(); + } return copy; } @@ -2064,10 +2040,7 @@ gtk_icon_source_free (GtkIconSource *source) { g_return_if_fail (source != NULL); - g_free ((char*) source->filename); - if (source->pixbuf) - g_object_unref (source->pixbuf); - + icon_source_clear (source); g_free (source); } @@ -2084,6 +2057,34 @@ gtk_icon_source_get_type (void) return our_type; } +static void +icon_source_clear (GtkIconSource *source) +{ + switch (source->type) + { + case GTK_ICON_SOURCE_EMPTY: + break; + case GTK_ICON_SOURCE_ICON_NAME: + g_free (source->source.icon_name); + source->source.icon_name = NULL; + break; + case GTK_ICON_SOURCE_FILENAME: + g_free (source->source.filename); + source->source.filename = NULL; + g_free (source->filename_pixbuf); + source->filename_pixbuf = NULL; + break; + case GTK_ICON_SOURCE_PIXBUF: + g_object_unref (source->source.pixbuf); + source->source.pixbuf = NULL; + break; + default: + g_assert_not_reached(); + } + + source->type = GTK_ICON_SOURCE_EMPTY; +} + /** * gtk_icon_source_set_filename: * @source: a #GtkIconSource @@ -2099,13 +2100,44 @@ gtk_icon_source_set_filename (GtkIconSource *source, g_return_if_fail (source != NULL); g_return_if_fail (filename == NULL || g_path_is_absolute (filename)); - if (source->filename == filename) + if (source->type == GTK_ICON_SOURCE_FILENAME && + source->source.filename == filename) return; - if (source->filename) - g_free (source->filename); + icon_source_clear (source); + + if (filename != NULL) + { + source->type = GTK_ICON_SOURCE_FILENAME; + source->source.filename = g_strdup (filename); + } +} - source->filename = g_strdup (filename); +/** + * gtk_icon_source_set_icon_name + * @source: a #GtkIconSource + * @icon_name: name of icon to use + * + * Sets the name of an icon to look up in the current icon theme + * to use as a base image when creating icon variants for #GtkIconSet. + **/ +void +gtk_icon_source_set_icon_name (GtkIconSource *source, + const gchar *icon_name) +{ + g_return_if_fail (source != NULL); + + if (source->type == GTK_ICON_SOURCE_ICON_NAME && + source->source.icon_name == icon_name) + return; + + icon_source_clear (source); + + if (icon_name != NULL) + { + source->type = GTK_ICON_SOURCE_ICON_NAME; + source->source.icon_name = g_strdup (icon_name); + } } /** @@ -2114,23 +2146,26 @@ gtk_icon_source_set_filename (GtkIconSource *source, * @pixbuf: pixbuf to use as a source * * Sets a pixbuf to use as a base image when creating icon variants - * for #GtkIconSet. If an icon source has both a filename and a pixbuf - * set, the pixbuf will take priority. - * + * for #GtkIconSet. **/ void gtk_icon_source_set_pixbuf (GtkIconSource *source, GdkPixbuf *pixbuf) { g_return_if_fail (source != NULL); + g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); + + if (source->type == GTK_ICON_SOURCE_PIXBUF && + source->source.pixbuf == pixbuf) + return; - if (pixbuf) - g_object_ref (pixbuf); - - if (source->pixbuf) - g_object_unref (source->pixbuf); - - source->pixbuf = pixbuf; + icon_source_clear (source); + + if (pixbuf != NULL) + { + source->type = GTK_ICON_SOURCE_PIXBUF; + source->source.pixbuf = g_object_ref (pixbuf); + } } /** @@ -2148,8 +2183,32 @@ G_CONST_RETURN gchar* gtk_icon_source_get_filename (const GtkIconSource *source) { g_return_val_if_fail (source != NULL, NULL); - - return source->filename; + + if (source->type == GTK_ICON_SOURCE_FILENAME) + return source->source.filename; + else + return NULL; +} + +/** + * gtk_icon_source_get_icon_name: + * @source: a #GtkIconSource + * + * Retrieves the source icon name, or %NULL if none is set. The + * icon_name is not a copy, and should not be modified or expected to + * persist beyond the lifetime of the icon source. + * + * Return value: icon name. This string must not be modified or freed. + **/ +G_CONST_RETURN gchar* +gtk_icon_source_get_icon_name (const GtkIconSource *source) +{ + g_return_val_if_fail (source != NULL, NULL); + + if (source->type == GTK_ICON_SOURCE_ICON_NAME) + return source->source.icon_name; + else + return NULL; } /** @@ -2157,7 +2216,12 @@ gtk_icon_source_get_filename (const GtkIconSource *source) * @source: a #GtkIconSource * * Retrieves the source pixbuf, or %NULL if none is set. - * The reference count on the pixbuf is not incremented. + * In addition, if a filename source is in use, this + * function in some cases will return the pixbuf from + * loaded from the filename. This is, for example, true + * for the GtkIconSource passed to the GtkStyle::render_icon() + * virtual function. The reference count on the pixbuf is + * not incremented. * * Return value: source pixbuf **/ @@ -2166,7 +2230,12 @@ gtk_icon_source_get_pixbuf (const GtkIconSource *source) { g_return_val_if_fail (source != NULL, NULL); - return source->pixbuf; + if (source->type == GTK_ICON_SOURCE_PIXBUF) + return source->source.pixbuf; + else if (source->type == GTK_ICON_SOURCE_FILENAME) + return source->filename_pixbuf; + else + return NULL; } /** @@ -2420,9 +2489,6 @@ gtk_icon_source_get_size (const GtkIconSource *source) return source->size; } -/* Note that the logical maximum is 20 per GtkTextDirection, so we could - * eventually set this to >20 to never throw anything out. - */ #define NUM_CACHED_ICONS 8 typedef struct _CachedIcon CachedIcon; @@ -2444,7 +2510,10 @@ static void ensure_cache_up_to_date (GtkIconSet *icon_set) { if (icon_set->cache_serial != cache_serial) - clear_cache (icon_set, TRUE); + { + clear_cache (icon_set, TRUE); + icon_set->cache_serial = cache_serial; + } } static void diff --git a/gtk/gtkiconfactory.h b/gtk/gtkiconfactory.h index 91ebdf6810..51fed289fd 100644 --- a/gtk/gtkiconfactory.h +++ b/gtk/gtkiconfactory.h @@ -142,11 +142,16 @@ void gtk_icon_source_free (GtkIconSource *so void gtk_icon_source_set_filename (GtkIconSource *source, const gchar *filename); +void gtk_icon_source_set_icon_name (GtkIconSource *source, + const gchar *icon_name); void gtk_icon_source_set_pixbuf (GtkIconSource *source, GdkPixbuf *pixbuf); +void gtk_icon_source_set_icon_name (GtkIconSource *source, + const gchar *icon_name); -G_CONST_RETURN gchar* gtk_icon_source_get_filename (const GtkIconSource *source); -GdkPixbuf* gtk_icon_source_get_pixbuf (const GtkIconSource *source); +G_CONST_RETURN gchar* gtk_icon_source_get_filename (const GtkIconSource *source); +G_CONST_RETURN gchar* gtk_icon_source_get_icon_name (const GtkIconSource *source); +GdkPixbuf* gtk_icon_source_get_pixbuf (const GtkIconSource *source); void gtk_icon_source_set_direction_wildcarded (GtkIconSource *source, gboolean setting); diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c new file mode 100644 index 0000000000..eca45e39a0 --- /dev/null +++ b/gtk/gtkicontheme.c @@ -0,0 +1,2535 @@ +/* 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gtkicontheme.h" +#include "gtkiconthemeparser.h" +#include "gtkintl.h" +#include "gtksettings.h" + +#define DEFAULT_THEME_NAME "hicolor" + +typedef struct _GtkIconData GtkIconData; + +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, +} IconSuffix; + + +struct _GtkIconThemePrivate +{ + guint custom_theme : 1; + guint is_screen_singleton : 1; + guint pixbuf_supports_svg : 1; + + char *current_theme; + char **search_path; + int search_path_len; + + gboolean themes_valid; + /* A list of all the themes needed to look up icons. + * In search order, without duplicates + */ + GList *themes; + GHashTable *unthemed_icons; + + /* Note: The keys of this hashtable are owned by the + * themedir and unthemed hashtables. + */ + GHashTable *all_icons; + + /* GdkScreen for the icon theme (may be NULL) + */ + GdkScreen *screen; + + /* time when we last stat:ed for theme changes */ + long last_stat_time; + GList *dir_mtimes; +}; + +struct _GtkIconInfo +{ + guint ref_count; + + /* Information about the source + */ + gchar *filename; + GdkPixbuf *builtin_pixbuf; + + GtkIconData *data; + + /* Information about the directory where + * the source was found + */ + IconThemeDirType dir_type; + gint dir_size; + gint threshold; + + /* Parameters influencing the scaled icon + */ + gint desired_size; + gboolean raw_coordinates; + + /* Cached information if we go ahead and try to load + * the icon. + */ + GdkPixbuf *pixbuf; + GError *load_error; + gdouble scale; +}; + +typedef struct +{ + char *name; + char *display_name; + char *comment; + char *example; + + /* In search order */ + GList *dirs; +} IconTheme; + +struct _GtkIconData +{ + gboolean has_embedded_rect; + gint x0, y0, x1, y1; + + GdkPoint *attach_points; + gint n_attach_points; + + gchar *display_name; +}; + +typedef struct +{ + IconThemeDirType type; + GQuark context; + + int size; + int min_size; + int max_size; + int threshold; + + char *dir; + + GHashTable *icons; + GHashTable *icon_data; +} IconThemeDir; + +typedef struct +{ + char *svg_filename; + char *no_svg_filename; +} UnthemedIcon; + +typedef struct +{ + gint size; + GdkPixbuf *pixbuf; +} BuiltinIcon; + +typedef struct +{ + char *dir; + time_t mtime; /* 0 == not existing or not a dir */ +} IconThemeDirMtime; + +static void gtk_icon_theme_class_init (GtkIconThemeClass *klass); +static void gtk_icon_theme_init (GtkIconTheme *icon_theme); +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 char *icon_name, + int size, + gboolean allow_svg, + gboolean use_default_icons); +static void theme_list_icons (IconTheme *theme, + GHashTable *icons, + GQuark context); +static void theme_subdir_load (GtkIconTheme *icon_theme, + IconTheme *theme, + GtkIconThemeFile *theme_file, + char *subdir); +static void do_theme_change (GtkIconTheme *icon_theme); + +static void blow_themes (GtkIconTheme *icon_themes); + +static void icon_data_free (GtkIconData *icon_data); + +static GtkIconInfo *icon_info_new (void); +static GtkIconInfo *icon_info_new_builtin (BuiltinIcon *icon); + +static IconSuffix suffix_from_name (const char *name); + +static BuiltinIcon *find_builtin_icon (const gchar *icon_name, + gint size, + gint *min_difference_p, + gboolean *has_larger_p); + +static guint signal_changed = 0; + +static GHashTable *icon_theme_builtin_icons; + +GType +gtk_icon_theme_get_type (void) +{ + static GType type = 0; + + if (type == 0) + { + static const GTypeInfo info = + { + sizeof (GtkIconThemeClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_icon_theme_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkIconTheme), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_icon_theme_init, + }; + + type = g_type_register_static (G_TYPE_OBJECT, "GtkIconTheme", &info, 0); + } + + return type; +} + +/** + * 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. + * + * Return value: the newly created #GtkIconTheme object. + **/ +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(). + * + * Return value: 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. + **/ +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. + * + * Return value: 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. + **/ +GtkIconTheme * +gtk_icon_theme_get_for_screen (GdkScreen *screen) +{ + GtkIconTheme *icon_theme; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (!screen->closed, 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), "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 ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkIconThemeClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (klass, sizeof (GtkIconThemePrivate)); +} + + +/* 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), "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) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + if (!priv->custom_theme) + { + gchar *theme = NULL; + + if (priv->screen) + { + GtkSettings *settings = gtk_settings_get_for_screen (priv->screen); + g_object_get (settings, "gtk-icon-theme-name", &theme, NULL); + } + + if (!theme) + theme = g_strdup (DEFAULT_THEME_NAME); + + if (strcmp (priv->current_theme, theme) != 0) + { + g_free (priv->current_theme); + priv->current_theme = theme; + + do_theme_change (icon_theme); + } + else + g_free (theme); + } +} + +/* 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); + 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. + **/ +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 () +{ + GSList *formats = gdk_pixbuf_get_formats (); + GSList *tmp_list; + gboolean 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_free (mime_types); + } + + g_slist_free (formats); + + return found_svg; +} + +static void +gtk_icon_theme_init (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + + priv = g_type_instance_get_private ((GTypeInstance *)icon_theme, + GTK_TYPE_ICON_THEME); + icon_theme->priv = priv; + + priv->custom_theme = FALSE; + priv->current_theme = g_strdup (DEFAULT_THEME_NAME); + priv->search_path = g_new (char *, 5); + + + priv->search_path[0] = g_build_filename (g_get_home_dir (), + ".icons", + NULL); + priv->search_path[1] = g_strdup (GTK_DATADIR "/pixmaps"); + priv->search_path[2] = g_strdup (GTK_DATADIR "/icons"); + priv->search_path[3] = g_strdup ("/usr/share/icons"); + priv->search_path[4] = g_strdup ("/usr/share/pixmaps"); + priv->search_path_len = 5; + + 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) +{ + g_free (dir_mtime->dir); + g_free (dir_mtime); +} + +static void +do_theme_change (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + blow_themes (icon_theme); + g_signal_emit (G_OBJECT (icon_theme), signal_changed, 0); + + if (priv->screen && priv->is_screen_singleton) + { + GtkSettings *settings = gtk_settings_get_for_screen (priv->screen); + _gtk_rc_reset_styles (settings); + } +} + +static void +blow_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + + if (priv->themes_valid) + { + g_hash_table_destroy (priv->all_icons); + g_list_foreach (priv->themes, (GFunc)theme_destroy, NULL); + g_list_free (priv->themes); + g_list_foreach (priv->dir_mtimes, (GFunc)free_dir_mtime, NULL); + g_list_free (priv->dir_mtimes); + g_hash_table_destroy (priv->unthemed_icons); + } + priv->themes = NULL; + priv->unthemed_icons = NULL; + priv->dir_mtimes = NULL; + priv->all_icons = 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; + + unset_screen (icon_theme); + + g_free (priv->current_theme); + priv->current_theme = NULL; + + for (i=0; i < priv->search_path_len; i++) + g_free (priv->search_path[i]); + + g_free (priv->search_path); + priv->search_path = NULL; + + blow_themes (icon_theme); +} + +/** + * gtk_icon_theme_set_search_path: + * @icon_theme: a #GtkIconTheme + * @path: 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. (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 default icon theme, which is called "hicolor", rather than + * directly on the icon path.) + **/ +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: 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(). + **/ +void +gtk_icon_theme_get_search_path (GtkIconTheme *icon_theme, + gchar **path[], + gint *n_elements) +{ + GtkIconThemePrivate *priv; + int 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] + 1); + (*path)[i] = NULL; + } +} + +/** + * gtk_icon_theme_append_search_path: + * @icon_theme: a #GtkIconTheme + * @path: directory name to append to the icon path + * + * Appends a directory to the search path. See gtk_icon_theme_set_search_path(). + **/ +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: directory name to prepend to the icon path + * + * Prepends a directory to the search path. See gtk_icon_theme_set_search_path(). + **/ +void +gtk_icon_theme_prepend_search_path (GtkIconTheme *icon_theme, + const gchar *path) +{ + GtkIconThemePrivate *priv; + int 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 = 0; i < priv->search_path_len - 1; i++) + priv->search_path[i+1] = priv->search_path[i]; + + priv->search_path[0] = g_strdup (path); + + do_theme_change (icon_theme); +} + +/** + * gtk_icon_theme_set_custom_theme: + * @icon_theme: a #GtkIconTheme + * @theme_name: name of icon theme to use instead of configured 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_default(). + **/ +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 (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 + { + priv->custom_theme = FALSE; + + update_current_theme (icon_theme); + } +} + +static void +insert_theme (GtkIconTheme *icon_theme, const char *theme_name) +{ + int i; + GList *l; + char **dirs; + char **themes; + GtkIconThemePrivate *priv; + IconTheme *theme; + char *path; + char *contents; + char *directories; + char *inherits; + GtkIconThemeFile *theme_file; + IconThemeDirMtime *dir_mtime; + struct stat 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_new (IconThemeDirMtime, 1); + dir_mtime->dir = path; + if (stat (path, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode)) + dir_mtime->mtime = stat_buf.st_mtime; + else + dir_mtime->mtime = 0; + + priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime); + } + + theme_file = NULL; + for (i = 0; i < priv->search_path_len; i++) + { + path = g_build_filename (priv->search_path[i], + theme_name, + "index.theme", + NULL); + if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) { + if (g_file_get_contents (path, &contents, NULL, NULL)) { + theme_file = _gtk_icon_theme_file_new_from_string (contents, NULL); + g_free (contents); + g_free (path); + break; + } + } + g_free (path); + } + + if (theme_file == NULL) + return; + + theme = g_new (IconTheme, 1); + if (!_gtk_icon_theme_file_get_locale_string (theme_file, + "Icon Theme", + "Name", + &theme->display_name)) + { + g_warning ("Theme file for %s has no name\n", theme_name); + g_free (theme); + _gtk_icon_theme_file_free (theme_file); + return; + } + + if (!_gtk_icon_theme_file_get_string (theme_file, + "Icon Theme", + "Directories", + &directories)) + { + g_warning ("Theme file for %s has no directories\n", theme_name); + g_free (theme->display_name); + g_free (theme); + _gtk_icon_theme_file_free (theme_file); + return; + } + + theme->name = g_strdup (theme_name); + _gtk_icon_theme_file_get_locale_string (theme_file, + "Icon Theme", + "Comment", + &theme->comment); + _gtk_icon_theme_file_get_string (theme_file, + "Icon Theme", + "Example", + &theme->example); + + dirs = g_strsplit (directories, ",", 0); + + theme->dirs = NULL; + for (i = 0; dirs[i] != NULL; i++) + theme_subdir_load (icon_theme, theme, theme_file, dirs[i]); + + g_strfreev (dirs); + + theme->dirs = g_list_reverse (theme->dirs); + + g_free (directories); + + /* Prepend the finished theme */ + priv->themes = g_list_prepend (priv->themes, theme); + + if (_gtk_icon_theme_file_get_string (theme_file, + "Icon Theme", + "Inherits", + &inherits)) + { + themes = g_strsplit (inherits, ",", 0); + + for (i = 0; themes[i] != NULL; i++) + insert_theme (icon_theme, themes[i]); + + g_strfreev (themes); + + g_free (inherits); + } + + _gtk_icon_theme_file_free (theme_file); +} + +static void +free_unthemed_icon (UnthemedIcon *unthemed_icon) +{ + if (unthemed_icon->svg_filename) + g_free (unthemed_icon->svg_filename); + if (unthemed_icon->svg_filename) + g_free (unthemed_icon->no_svg_filename); + g_free (unthemed_icon); +} + +static void +load_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + GDir *gdir; + int base; + char *dir, *base_name, *dot; + const char *file; + char *abs_file; + UnthemedIcon *unthemed_icon; + IconSuffix old_suffix, new_suffix; + struct timeval tv; + + priv = icon_theme->priv; + + priv->all_icons = g_hash_table_new (g_str_hash, g_str_equal); + + insert_theme (icon_theme, priv->current_theme); + + /* Always look in the "default" icon theme */ + insert_theme (icon_theme, DEFAULT_THEME_NAME); + 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]; + gdir = g_dir_open (dir, 0, NULL); + + if (gdir == NULL) + continue; + + while ((file = g_dir_read_name (gdir))) + { + new_suffix = suffix_from_name (file); + + if (new_suffix != ICON_SUFFIX_NONE) + { + abs_file = g_build_filename (dir, file, NULL); + + base_name = g_strdup (file); + + dot = strrchr (base_name, '.'); + if (dot) + *dot = 0; + + if ((unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, + base_name))) + { + if (new_suffix == ICON_SUFFIX_SVG) + { + if (unthemed_icon->no_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_new0 (UnthemedIcon, 1); + + if (new_suffix == ICON_SUFFIX_SVG) + unthemed_icon->svg_filename = abs_file; + else + unthemed_icon->svg_filename = abs_file; + + g_hash_table_insert (priv->unthemed_icons, + base_name, + unthemed_icon); + g_hash_table_insert (priv->all_icons, + base_name, NULL); + } + } + } + g_dir_close (gdir); + } + + priv->themes_valid = TRUE; + + gettimeofday(&tv, NULL); + priv->last_stat_time = tv.tv_sec; +} + +static void +ensure_valid_themes (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv = icon_theme->priv; + struct timeval tv; + + if (priv->themes_valid) + { + gettimeofday(&tv, NULL); + + if (ABS (tv.tv_sec - priv->last_stat_time) > 5) + gtk_icon_theme_rescan_if_needed (icon_theme); + } + + if (!priv->themes_valid) + load_themes (icon_theme); +} + +/** + * 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 structure 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.) + * + * Return value: a #GtkIconInfo structure containing information + * about the icon, or %NULL if the icon wasn't found. Free with + * gtk_icon_info_free() + **/ +GtkIconInfo * +gtk_icon_theme_lookup_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags) +{ + GtkIconThemePrivate *priv; + GList *l; + GtkIconInfo *icon_info = NULL; + UnthemedIcon *unthemed_icon; + gboolean allow_svg; + gboolean use_builtin; + gboolean found_default; + + 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); + + priv = icon_theme->priv; + + 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); + + ensure_valid_themes (icon_theme); + + found_default = FALSE; + l = priv->themes; + while (l != NULL) + { + IconTheme *icon_theme = l->data; + + if (strcmp (icon_theme->name, DEFAULT_THEME_NAME) == 0) + found_default = TRUE; + + icon_info = theme_lookup_icon (icon_theme, icon_name, size, allow_svg, use_builtin); + if (icon_info) + goto out; + + l = l->next; + } + + if (!found_default) + { + BuiltinIcon *builtin = find_builtin_icon (icon_name, size, NULL, NULL); + if (builtin) + { + icon_info = icon_info_new_builtin (builtin); + goto out; + } + } + + unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, icon_name); + if (unthemed_icon) + { + icon_info = icon_info_new (); + + /* 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); + + icon_info->dir_type = ICON_THEME_DIR_UNTHEMED; + } + + out: + if (icon_info) + icon_info->desired_size = size; + + return icon_info; +} + +/* Error quark */ +GQuark +gtk_icon_theme_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string ("gtk-icon-theme-error-quark"); + + return q; +} + +/** + * 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: 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(). + * + * Return value: 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. + **/ +GdkPixbuf * +gtk_icon_theme_load_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + 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); + + icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, + 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"), icon_name); + return NULL; + } + + pixbuf = gtk_icon_info_load_icon (icon_info, error); + gtk_icon_info_free (icon_info); + + return pixbuf; +} + +/** + * 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. + * + * Return value: %TRUE if @icon_theme includes an + * icon for @icon_name. + **/ +gboolean +gtk_icon_theme_has_icon (GtkIconTheme *icon_theme, + const char *icon_name) +{ + GtkIconThemePrivate *priv; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), FALSE); + + priv = icon_theme->priv; + + ensure_valid_themes (icon_theme); + + if (g_hash_table_lookup_extended (priv->all_icons, + icon_name, NULL, NULL)) + return TRUE; + if (g_hash_table_lookup_extended (icon_theme_builtin_icons, + icon_name, NULL, NULL)) + return TRUE; + + return FALSE; +} + + +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: 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 'apps' and + * 'mimetypes'. + * + * Return value: 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(). + **/ +GList * +gtk_icon_theme_list_icons (GtkIconTheme *icon_theme, + const char *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 = 0; + + g_hash_table_foreach (icons, + add_key_to_list, + &list); + + g_hash_table_destroy (icons); + + 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.) + * + * Return value: the name of an example icon or %NULL. + * Free with g_free(). + **/ +char * +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; +} + +/** + * 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. + * + * Return value: %TRUE if the icon theme has changed and needed + * to be reloaded. + **/ +gboolean +gtk_icon_theme_rescan_if_needed (GtkIconTheme *icon_theme) +{ + GtkIconThemePrivate *priv; + IconThemeDirMtime *dir_mtime; + GList *d; + int stat_res; + struct stat stat_buf; + struct timeval tv; + + g_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), FALSE); + + priv = icon_theme->priv; + + for (d = priv->dir_mtimes; d != NULL; d = d->next) + { + dir_mtime = d->data; + + stat_res = stat (dir_mtime->dir, &stat_buf); + + /* dir mtime didn't change */ + if (stat_res == 0 && + 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->mtime == 0 && + (stat_res != 0 || !S_ISDIR (stat_buf.st_mode))) + continue; + + do_theme_change (icon_theme); + return TRUE; + } + + gettimeofday (&tv, NULL); + priv->last_stat_time = tv.tv_sec; + + return FALSE; +} + +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_foreach (theme->dirs, (GFunc)theme_dir_destroy, NULL); + g_list_free (theme->dirs); + g_free (theme); +} + +static void +theme_dir_destroy (IconThemeDir *dir) +{ + g_hash_table_destroy (dir->icons); + if (dir->icon_data) + g_hash_table_destroy (dir->icon_data); + g_free (dir->dir); + g_free (dir); +} + +static int +theme_dir_size_difference (IconThemeDir *dir, int size, gboolean *smaller) +{ + int min, max; + switch (dir->type) + { + case ICON_THEME_DIR_FIXED: + *smaller = size < dir->size; + return abs (size - dir->size); + break; + case ICON_THEME_DIR_SCALABLE: + *smaller = size < dir->min_size; + if (size < dir->min_size) + return dir->min_size - size; + if (size > dir->max_size) + return size - dir->max_size; + return 0; + break; + case ICON_THEME_DIR_THRESHOLD: + min = dir->size - dir->threshold; + max = dir->size + dir->threshold; + *smaller = size < min; + if (size < min) + return min - size; + if (size > max) + return size - max; + return 0; + break; + case ICON_THEME_DIR_UNTHEMED: + g_assert_not_reached (); + break; + } + g_assert_not_reached (); + return 1000; +} + +static const char * +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"; + default: + g_assert_not_reached(); + } + return NULL; +} + +static IconSuffix +suffix_from_name (const char *name) +{ + IconSuffix retval; + + 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; + else + retval = ICON_SUFFIX_NONE; + + return retval; +} + +static IconSuffix +best_suffix (IconSuffix suffix, + gboolean allow_svg) +{ + 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 GtkIconInfo * +theme_lookup_icon (IconTheme *theme, + const char *icon_name, + int size, + gboolean allow_svg, + gboolean use_builtin) +{ + GList *l; + IconThemeDir *dir, *min_dir; + char *file; + int min_difference, difference; + BuiltinIcon *closest_builtin = NULL;; + gboolean smaller, has_larger; + IconSuffix suffix; + + min_difference = G_MAXINT; + min_dir = NULL; + has_larger = FALSE; + + /* Builtin icons are logically part of the default theme and + * are searched before other subdirectories of the default theme. + */ + if (strcmp (theme->name, DEFAULT_THEME_NAME) == 0 && use_builtin) + { + closest_builtin = find_builtin_icon (icon_name, size, + &min_difference, + &has_larger); + + if (min_difference == 0) + return icon_info_new_builtin (closest_builtin); + } + + l = theme->dirs; + while (l != NULL) + { + dir = l->data; + + suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name)); + + if (suffix != ICON_SUFFIX_NONE && + (allow_svg || suffix != ICON_SUFFIX_SVG)) + { + difference = theme_dir_size_difference (dir, size, &smaller); + + if (difference == 0) + { + min_dir = dir; + break; + } + + if (!has_larger) + { + if (difference < min_difference || smaller) + { + min_difference = difference; + min_dir = dir; + closest_builtin = NULL; + has_larger = smaller; + } + } + else + { + if (difference < min_difference && smaller) + { + min_difference = difference; + min_dir = dir; + closest_builtin = NULL; + } + } + + } + + l = l->next; + } + + if (closest_builtin) + return icon_info_new_builtin (closest_builtin); + + if (min_dir) + { + GtkIconInfo *icon_info = icon_info_new (); + + suffix = GPOINTER_TO_UINT (g_hash_table_lookup (min_dir->icons, icon_name)); + suffix = best_suffix (suffix, allow_svg); + g_assert (suffix != ICON_SUFFIX_NONE); + + file = g_strconcat (icon_name, string_from_suffix (suffix), NULL); + icon_info->filename = g_build_filename (min_dir->dir, file, NULL); + g_free (file); + + if (min_dir->icon_data != NULL) + icon_info->data = g_hash_table_lookup (min_dir->icon_data, icon_name); + + icon_info->dir_type = min_dir->type; + icon_info->dir_size = min_dir->size; + icon_info->threshold = min_dir->threshold; + + return icon_info; + } + + 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) + g_hash_table_foreach (dir->icons, + add_key_to_hash, + icons); + + l = l->next; + } +} + +static void +load_icon_data (IconThemeDir *dir, const char *path, const char *name) +{ + GtkIconThemeFile *icon_file; + char *base_name; + char **split; + char *contents; + char *dot; + char *str; + char *split_point; + int i; + + GtkIconData *data; + + if (g_file_get_contents (path, &contents, NULL, NULL)) + { + icon_file = _gtk_icon_theme_file_new_from_string (contents, NULL); + + if (icon_file) + { + base_name = g_strdup (name); + dot = strrchr (base_name, '.'); + *dot = 0; + + data = g_new0 (GtkIconData, 1); + g_hash_table_replace (dir->icon_data, base_name, data); + + if (_gtk_icon_theme_file_get_string (icon_file, "Icon Data", + "EmbeddedTextRectangle", + &str)) + { + split = g_strsplit (str, ",", 4); + + i = 0; + while (split[i] != NULL) + i++; + + if (i==4) + { + data->has_embedded_rect = TRUE; + data->x0 = atoi (split[0]); + data->y0 = atoi (split[1]); + data->x1 = atoi (split[2]); + data->y1 = atoi (split[3]); + } + + g_strfreev (split); + g_free (str); + } + + + if (_gtk_icon_theme_file_get_string (icon_file, "Icon Data", + "AttachPoints", + &str)) + { + split = g_strsplit (str, "|", -1); + + i = 0; + while (split[i] != NULL) + i++; + + data->n_attach_points = i; + data->attach_points = g_malloc (sizeof (GdkPoint) * i); + + i = 0; + while (split[i] != NULL && i < data->n_attach_points) + { + split_point = strchr (split[i], ','); + if (split_point) + { + *split_point = 0; + split_point++; + data->attach_points[i].x = atoi (split[i]); + data->attach_points[i].y = atoi (split_point); + } + i++; + } + + g_strfreev (split); + g_free (str); + } + + _gtk_icon_theme_file_get_locale_string (icon_file, "Icon Data", + "DisplayName", + &data->display_name); + + _gtk_icon_theme_file_free (icon_file); + } + g_free (contents); + } + +} + +static void +scan_directory (GtkIconThemePrivate *icon_theme, + IconThemeDir *dir, char *full_dir) +{ + GDir *gdir; + const char *name; + char *base_name, *dot; + char *path; + IconSuffix suffix, hash_suffix; + + dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + gdir = g_dir_open (full_dir, 0, NULL); + + if (gdir == NULL) + return; + + while ((name = g_dir_read_name (gdir))) + { + if (g_str_has_suffix (name, ".icon")) + { + if (dir->icon_data == NULL) + dir->icon_data = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)icon_data_free); + + path = g_build_filename (full_dir, name, NULL); + if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) + load_icon_data (dir, path, name); + + g_free (path); + + continue; + } + + suffix = suffix_from_name (name); + if (suffix == ICON_SUFFIX_NONE) + continue; + + base_name = g_strdup (name); + dot = strrchr (base_name, '.'); + *dot = 0; + + hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (dir->icons, base_name)); + g_hash_table_replace (dir->icons, base_name, GUINT_TO_POINTER (hash_suffix| suffix)); + g_hash_table_insert (icon_theme->all_icons, base_name, NULL); + } + + g_dir_close (gdir); +} + +static void +theme_subdir_load (GtkIconTheme *icon_theme, + IconTheme *theme, + GtkIconThemeFile *theme_file, + char *subdir) +{ + int base; + char *type_string; + IconThemeDir *dir; + IconThemeDirType type; + char *context_string; + GQuark context; + int size; + int min_size; + int max_size; + int threshold; + char *full_dir; + + if (!_gtk_icon_theme_file_get_integer (theme_file, + subdir, + "Size", + &size)) + { + g_warning ("Theme directory %s of theme %s has no size field\n", subdir, theme->name); + return; + } + + type = ICON_THEME_DIR_THRESHOLD; + if (_gtk_icon_theme_file_get_string (theme_file, subdir, "Type", &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; + if (_gtk_icon_theme_file_get_string (theme_file, subdir, "Context", &context_string)) + { + context = g_quark_from_string (context_string); + g_free (context_string); + } + + if (!_gtk_icon_theme_file_get_integer (theme_file, + subdir, + "MaxSize", + &max_size)) + max_size = size; + + if (!_gtk_icon_theme_file_get_integer (theme_file, + subdir, + "MinSize", + &min_size)) + min_size = size; + + if (!_gtk_icon_theme_file_get_integer (theme_file, + subdir, + "Threshold", + &threshold)) + threshold = 2; + + for (base = 0; base < icon_theme->priv->search_path_len; base++) + { + full_dir = g_build_filename (icon_theme->priv->search_path[base], + theme->name, + subdir, + NULL); + if (g_file_test (full_dir, G_FILE_TEST_IS_DIR)) + { + dir = g_new (IconThemeDir, 1); + dir->type = type; + dir->context = context; + dir->size = size; + dir->min_size = min_size; + dir->max_size = max_size; + dir->threshold = threshold; + dir->dir = full_dir; + dir->icon_data = NULL; + + scan_directory (icon_theme->priv, dir, full_dir); + + theme->dirs = g_list_append (theme->dirs, dir); + } + else + g_free (full_dir); + } +} + +static void +icon_data_free (GtkIconData *icon_data) +{ + g_free (icon_data->attach_points); + g_free (icon_data->display_name); + g_free (icon_data); +} + +/* + * GtkIconInfo + */ +GType +gtk_icon_info_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + our_type = g_boxed_type_register_static ("GtkIconInfo", + (GBoxedCopyFunc) gtk_icon_info_copy, + (GBoxedFreeFunc) gtk_icon_info_free); + + return our_type; +} + +static GtkIconInfo * +icon_info_new (void) +{ + GtkIconInfo *icon_info = g_new0 (GtkIconInfo, 1); + + icon_info->ref_count = 1; + icon_info->scale = -1.; + + return icon_info; +} + +static GtkIconInfo * +icon_info_new_builtin (BuiltinIcon *icon) +{ + GtkIconInfo *icon_info = icon_info_new (); + + icon_info->builtin_pixbuf = g_object_ref (icon->pixbuf); + icon_info->dir_type = ICON_THEME_DIR_THRESHOLD; + icon_info->dir_size = icon->size; + icon_info->threshold = 2; + + return icon_info; +} + +/** + * gtk_icon_info_copy: + * @icon_info: a #GtkIconInfo + * + * Make a copy of a #GtkIconInfo. + * + * Return value: the new GtkIconInfo + **/ +GtkIconInfo * +gtk_icon_info_copy (GtkIconInfo *icon_info) +{ + GtkIconInfo *copy; + + g_return_val_if_fail (icon_info != NULL, NULL); + + copy = g_memdup (icon_info, sizeof (GtkIconInfo)); + if (copy->builtin_pixbuf) + g_object_ref (copy->builtin_pixbuf); + if (copy->pixbuf) + g_object_ref (copy->pixbuf); + if (copy->load_error) + copy->load_error = g_error_copy (copy->load_error); + if (copy->filename) + copy->filename = g_strdup (copy->filename); + + return copy; +} + +/** + * gtk_icon_info_free: + * @icon_info: a #GtkIconInfo + * + * Free a #GtkIconInfo and associated information + **/ +void +gtk_icon_info_free (GtkIconInfo *icon_info) +{ + g_return_if_fail (icon_info != NULL); + + if (icon_info->filename) + g_free (icon_info->filename); + if (icon_info->builtin_pixbuf) + g_object_unref (icon_info->builtin_pixbuf); + if (icon_info->pixbuf) + g_object_unref (icon_info->pixbuf); + + g_free (icon_info); +} + +/** + * 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. + * + * Return value: the base size, or 0, if no base + * size is known for the icon. + **/ +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_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(). + * + * Return value: 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. + **/ +G_CONST_RETURN 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 structure + * + * 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(). + * + * Return value: 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. + **/ +GdkPixbuf * +gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + + return icon_info->builtin_pixbuf; +} + +static GdkPixbuf * +load_svg_at_size (const gchar *filename, + gint size, + GError **error) +{ + GdkPixbuf *pixbuf = NULL; + GdkPixbufLoader *loader = NULL; + gchar *contents; + gsize length; + + if (!g_file_get_contents (filename, + &contents, &length, error)) + goto bail; + + loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_set_size (loader, size, size); + + if (!gdk_pixbuf_loader_write (loader, contents, length, error)) + { + gdk_pixbuf_loader_close (loader, NULL); + goto bail; + } + + if (!gdk_pixbuf_loader_close (loader, error)) + goto bail; + + pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)); + + bail: + if (loader) + g_object_unref (loader); + + return pixbuf; +} + +/* This function contains the complicatd 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, + gboolean scale_only) +{ + int image_width, image_height; + GdkPixbuf *source_pixbuf; + + /* First check if we already succeeded have the necessary + * information (or failed earlier) + */ + if (scale_only && icon_info->scale >= 0) + return TRUE; + + if (icon_info->pixbuf) + return TRUE; + + if (icon_info->load_error) + return FALSE; + + /* SVG icons are a special case - we just immediately scale them + * to the desired size + */ + if (icon_info->filename && g_str_has_suffix (icon_info->filename, ".svg")) + { + icon_info->scale = icon_info->desired_size / 1000.; + + if (scale_only) + return TRUE; + + icon_info->pixbuf = load_svg_at_size (icon_info->filename, + icon_info->desired_size, + &icon_info->load_error); + + return icon_info->pixbuf != NULL; + } + + /* 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->dir_type == ICON_THEME_DIR_FIXED) + icon_info->scale = 1.0; + else if (icon_info->dir_type == ICON_THEME_DIR_THRESHOLD) + { + if (icon_info->desired_size >= icon_info->dir_size - icon_info->threshold && + icon_info->desired_size <= icon_info->dir_size + icon_info->threshold) + icon_info->scale = 1.0; + else if (icon_info->dir_size > 0) + icon_info->scale =(gdouble) icon_info->desired_size / icon_info->dir_size; + } + else if (icon_info->dir_type == ICON_THEME_DIR_SCALABLE) + { + if (icon_info->dir_size > 0) + icon_info->scale = (gdouble) icon_info->desired_size / icon_info->dir_size; + } + + if (icon_info->scale >= 0. && scale_only) + return TRUE; + + /* At this point, we need to actually get the icon; either from the + * builting image or by loading the file + */ + if (icon_info->builtin_pixbuf) + source_pixbuf = g_object_ref (icon_info->builtin_pixbuf); + else + { + source_pixbuf = gdk_pixbuf_new_from_file (icon_info->filename, + &icon_info->load_error); + if (!source_pixbuf) + 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); + + if (icon_info->scale < 0.0) + { + gint image_size = MAX (image_width, image_height); + if (image_size > 0) + icon_info->scale = icon_info->desired_size / image_size; + else + icon_info->scale = 1.0; + + if (icon_info->dir_type == ICON_THEME_DIR_UNTHEMED) + icon_info->scale = MAX (icon_info->scale, 1.0); + } + + /* We don't short-circuit out here for scale_only, since, now + * we've loaded the icon, we might as well go ahead and finish + * the job. This is a bit of a waste when we scale here + * and never get the final pixbuf; at the cost of a bit of + * extra complexity, we could keep the source pixbuf around + * but not actually scale it until neede. + */ + + if (icon_info->scale == 1.0) + icon_info->pixbuf = source_pixbuf; + else + { + icon_info->pixbuf = gdk_pixbuf_scale_simple (source_pixbuf, + 0.5 + image_width * icon_info->scale, + 0.5 + image_height * icon_info->scale, + GDK_INTERP_BILINEAR); + g_object_unref (source_pixbuf); + } + + return TRUE; +} + +/** + * gtk_icon_info_load_icon: + * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon() + * @error: + * + * Renders an icon previously looked up in an icon theme using + * gtk_icon_theme_lookup_icon(); the size will be based on the size + * pssed 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. (This maintains sharpness.) + * + * Return value: 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. + **/ +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 (icon_info != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + icon_info_ensure_scale_and_pixbuf (icon_info, FALSE); + + if (icon_info->load_error) + { + g_propagate_error (error, icon_info->load_error); + return NULL; + } + + return g_object_ref (icon_info->pixbuf); +} + +/** + * 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. + **/ +void +gtk_icon_info_set_raw_coordinates (GtkIconInfo *icon_info, + gboolean raw_coordinates) +{ + g_return_if_fail (icon_info != NULL); + + icon_info->raw_coordinates = raw_coordinates != FALSE; +} + +/* Scale coordinates from the icon data prior to returning + * them to the user. + */ +static gboolean +icon_info_scale_point (GtkIconInfo *icon_info, + gint x, + gint y, + gint *x_out, + gint *y_out) +{ + if (icon_info->raw_coordinates) + { + *x_out = x; + *y_out = y; + } + else + { + if (!icon_info_ensure_scale_and_pixbuf (icon_info, TRUE)) + return FALSE; + + *x_out = 0.5 + x * icon_info->scale; + *y_out = 0.5 + y * icon_info->scale; + } + + return TRUE; +} + +/** + * gtk_icon_info_get_embedded_rect: + * @icon_info: a #GtkIconInfo + * @rectangle: #GdkRectangle in which to store embedded + * rectangle coordinates; coordinates are only stored + * when this function returns %TRUE. + * + * Gets the coordinates of a rectangle within the icon + * that can be used for display of information such + * as a preview of the contents of a text file. + * See gtk_icon_info_set_raw_coordinates() for further + * information about the coordinate system. + * + * Return value: %TRUE if the icon has an embedded rectangle + **/ +gboolean +gtk_icon_info_get_embedded_rect (GtkIconInfo *icon_info, + GdkRectangle *rectangle) +{ + g_return_val_if_fail (icon_info != NULL, FALSE); + + if (icon_info->data && icon_info->data->has_embedded_rect && + icon_info_ensure_scale_and_pixbuf (icon_info, TRUE)) + { + gint scaled_x0, scaled_y0; + gint scaled_x1, scaled_y1; + + if (rectangle) + { + icon_info_scale_point (icon_info, + icon_info->data->x0, icon_info->data->y0, + &scaled_x0, &scaled_y0); + icon_info_scale_point (icon_info, + icon_info->data->x0, icon_info->data->y0, + &scaled_x1, &scaled_y1); + + rectangle->x = scaled_x0; + rectangle->y = scaled_y0; + rectangle->width = scaled_x1 - rectangle->x; + rectangle->height = scaled_y1 - rectangle->y; + } + + return TRUE; + } + else + return FALSE; +} + +/** + * gtk_icon_info_get_attach_points: + * @icon_info: a #GtkIconInfo + * @points: location to store pointer to an array of points, or %NULL + * free the array of points with g_free(). + * @n_points: location to store the number of points in @points, or %NULL + * + * Fetches the set of attach points for an icon. An attach point + * is a location in the icon that can be used as anchor points for attaching + * emblems or overlays to the icon. + * + * Return value: %TRUE if there are any attach points for the icon. + **/ +gboolean +gtk_icon_info_get_attach_points (GtkIconInfo *icon_info, + GdkPoint **points, + gint *n_points) +{ + g_return_val_if_fail (icon_info != NULL, FALSE); + + if (icon_info->data && icon_info->data->n_attach_points && + icon_info_ensure_scale_and_pixbuf (icon_info, TRUE)) + { + if (points) + { + gint i; + + *points = g_new (GdkPoint, icon_info->data->n_attach_points); + for (i = 0; i < icon_info->data->n_attach_points; i++) + icon_info_scale_point (icon_info, + icon_info->data->attach_points[i].x, + icon_info->data->attach_points[i].y, + &(*points)[i].x, + &(*points)[i].y); + } + + if (n_points) + *n_points = icon_info->data->n_attach_points; + + return TRUE; + } + else + { + if (points) + *points = NULL; + if (n_points) + *n_points = 0; + + return FALSE; + } +} + +/** + * gtk_icon_info_get_display_name: + * @icon_info: a #GtkIconInfo + * + * Gets the display name for an icon. A display name is a + * string to be used in place of the icon name in a user + * visible context like a list of icons. + * + * Return value: the display name for the icon or %NULL, if + * the icon doesn't have a specified display name. This value + * is owned @icon_info and must not be modified or free. + **/ +G_CONST_RETURN gchar * +gtk_icon_info_get_display_name (GtkIconInfo *icon_info) +{ + g_return_val_if_fail (icon_info != NULL, NULL); + + if (icon_info->data) + return icon_info->data->display_name; + else + return NULL; +} + +/* + * Builtin icons + */ + + +/** + * gtk_icon_theme_add_builtin_icon: + * @icon_name: the name of the icon to register + * @size: the size 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 (). + **/ +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 *min_difference_p, + gboolean *has_larger_p) +{ + GSList *icons = NULL; + gint min_difference = G_MAXINT; + gboolean has_larger = FALSE; + BuiltinIcon *min_icon = NULL; + + if (!icon_theme_builtin_icons) + return NULL; + + 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; + if (size > max) + difference = size - max; + else + difference = 0; + + if (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; + if (has_larger_p) + *has_larger_p = has_larger; + + return min_icon; +} diff --git a/gtk/gtkicontheme.h b/gtk/gtkicontheme.h new file mode 100644 index 0000000000..3c789e1a15 --- /dev/null +++ b/gtk/gtkicontheme.h @@ -0,0 +1,161 @@ +/* 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_ICON_THEME_H__ +#define __GTK_ICON_THEME_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_ICON_INFO (gtk_icon_info_get_type) + +#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_CHECK_GET_CLASS ((obj), GTK_TYPE_ICON_THEME, GtkIconThemeClass)) + +typedef struct _GtkIconInfo GtkIconInfo; +typedef struct _GtkIconTheme GtkIconTheme; +typedef struct _GtkIconThemeClass GtkIconThemeClass; +typedef struct _GtkIconThemePrivate GtkIconThemePrivate; + +struct _GtkIconTheme +{ + /*< private >*/ + GObject parent_instance; + + GtkIconThemePrivate *priv; +}; + +struct _GtkIconThemeClass +{ + GObjectClass parent_class; + + void (* changed) (GtkIconTheme *icon_theme); +}; + +/** + * GtkIconLookupFlags: + * @GTK_ICON_LOOKUP_NO_SVG: Never return SVG icons, even if gdk-pixbuf + * supports them. Cannot be used together with %GTK_ICON_LOOKUP_FORCE_SVG. + * @GTK_ICON_LOOKUP_FORCE_SVG: Return 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, gdk_icon_info_get_filename() + * returns %NULL and you need to call gdk_icon_info_get_builtin_pixbuf(). + * + * Used to specify options for gtk_icon_theme_lookup_icon() + **/ +typedef enum +{ + GTK_ICON_LOOKUP_NO_SVG = 0 << 0, + GTK_ICON_LOOKUP_FORCE_SVG = 0 << 1, + GTK_ICON_LOOKUP_USE_BUILTIN = 0 << 2 +} GtkIconLookupFlags; + +#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; + +GQuark gtk_icon_theme_error_quark (void) G_GNUC_CONST; + +GType gtk_icon_theme_get_type (void) G_GNUC_CONST; + +GtkIconTheme *gtk_icon_theme_new (void); +GtkIconTheme *gtk_icon_theme_get_default (void); +GtkIconTheme *gtk_icon_theme_get_for_screen (GdkScreen *screen); +void gtk_icon_theme_set_screen (GtkIconTheme *icon_theme, + GdkScreen *screen); + +void gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme, + const gchar *path[], + gint n_elements); +void gtk_icon_theme_get_search_path (GtkIconTheme *icon_theme, + gchar **path[], + gint *n_elements); +void gtk_icon_theme_append_search_path (GtkIconTheme *icon_theme, + const gchar *path); +void gtk_icon_theme_prepend_search_path (GtkIconTheme *icon_theme, + const gchar *path); + +void gtk_icon_theme_set_custom_theme (GtkIconTheme *icon_theme, + const gchar *theme_name); + +gboolean gtk_icon_theme_has_icon (GtkIconTheme *icon_theme, + const gchar *icon_name); +GtkIconInfo * gtk_icon_theme_lookup_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags); +GdkPixbuf * gtk_icon_theme_load_icon (GtkIconTheme *icon_theme, + const gchar *icon_name, + gint size, + GtkIconLookupFlags flags, + GError **error); + +GList * gtk_icon_theme_list_icons (GtkIconTheme *icon_theme, + const gchar *context); +char * gtk_icon_theme_get_example_icon_name (GtkIconTheme *icon_theme); + +gboolean gtk_icon_theme_rescan_if_needed (GtkIconTheme *icon_theme); + +void gtk_icon_theme_add_builtin_icon (const gchar *icon_name, + gint size, + GdkPixbuf *pixbuf); + +GType gtk_icon_info_get_type (void); +GtkIconInfo *gtk_icon_info_copy (GtkIconInfo *icon_info); +void gtk_icon_info_free (GtkIconInfo *icon_info); + +gint gtk_icon_info_get_base_size (GtkIconInfo *icon_info); +G_CONST_RETURN gchar *gtk_icon_info_get_filename (GtkIconInfo *icon_info); +GdkPixbuf * gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info); +GdkPixbuf * gtk_icon_info_load_icon (GtkIconInfo *icon_info, + GError **error); + +void gtk_icon_info_set_raw_coordinates (GtkIconInfo *icon_info, + gboolean raw_coordinates); + +gboolean gtk_icon_info_get_embedded_rect (GtkIconInfo *icon_info, + GdkRectangle *rectangle); +gboolean gtk_icon_info_get_attach_points (GtkIconInfo *icon_info, + GdkPoint **points, + gint *n_points); +G_CONST_RETURN gchar *gtk_icon_info_get_display_name (GtkIconInfo *icon_info); + +G_END_DECLS + +#endif /* __GTK_ICON_THEME_H__ */ diff --git a/gtk/gtkiconthemeparser.c b/gtk/gtkiconthemeparser.c new file mode 100644 index 0000000000..ad6bbdfa91 --- /dev/null +++ b/gtk/gtkiconthemeparser.c @@ -0,0 +1,854 @@ +/* GtkIconThemeParser - a parser of icon-theme files + * gtk-icon-theme-parser.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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "gtkiconthemeparser.h" + +typedef struct _GtkIconThemeFileSection GtkIconThemeFileSection; +typedef struct _GtkIconThemeFileLine GtkIconThemeFileLine; +typedef struct _GtkIconThemeFileParser GtkIconThemeFileParser; + +struct _GtkIconThemeFileSection { + GQuark section_name; /* 0 means just a comment block (before any section) */ + gint n_lines; + GtkIconThemeFileLine *lines; +}; + +struct _GtkIconThemeFileLine { + GQuark key; /* 0 means comment or blank line in value */ + char *locale; + gchar *value; +}; + +struct _GtkIconThemeFile { + gint n_sections; + GtkIconThemeFileSection *sections; + char *current_locale[2]; +}; + +struct _GtkIconThemeFileParser { + GtkIconThemeFile *df; + gint current_section; + gint n_allocated_lines; + gint n_allocated_sections; + gint line_nr; + char *line; +}; + +#define VALID_KEY_CHAR 1 +#define VALID_LOCALE_CHAR 2 +static const guchar valid[256] = { + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , +}; + +static void report_error (GtkIconThemeFileParser *parser, + char *message, + GtkIconThemeFileParseError error_code, + GError **error); +static GtkIconThemeFileSection *lookup_section (GtkIconThemeFile *df, + const char *section); +static GtkIconThemeFileLine * lookup_line (GtkIconThemeFile *df, + GtkIconThemeFileSection *section, + const char *keyname, + const char *locale); + + +GQuark +_gtk_icon_theme_file_parse_error_quark (void) +{ + static GQuark quark; + if (!quark) + quark = g_quark_from_static_string ("g_desktop_parse_error"); + + return quark; +} + +static void +parser_free (GtkIconThemeFileParser *parser) +{ + _gtk_icon_theme_file_free (parser->df); +} + +static void +gtk_icon_theme_file_line_free (GtkIconThemeFileLine *line) +{ + g_free (line->locale); + g_free (line->value); +} + +static void +gtk_icon_theme_file_section_free (GtkIconThemeFileSection *section) +{ + int i; + + for (i = 0; i < section->n_lines; i++) + gtk_icon_theme_file_line_free (§ion->lines[i]); + + g_free (section->lines); +} + +void +_gtk_icon_theme_file_free (GtkIconThemeFile *df) +{ + int i; + + for (i = 0; i < df->n_sections; i++) + gtk_icon_theme_file_section_free (&df->sections[i]); + g_free (df->sections); + g_free (df->current_locale[0]); + g_free (df->current_locale[1]); + + g_free (df); +} + +static void +grow_lines (GtkIconThemeFileParser *parser) +{ + int new_n_lines; + GtkIconThemeFileSection *section; + + if (parser->n_allocated_lines == 0) + new_n_lines = 1; + else + new_n_lines = parser->n_allocated_lines*2; + + section = &parser->df->sections[parser->current_section]; + + section->lines = g_realloc (section->lines, + sizeof (GtkIconThemeFileLine) * new_n_lines); + parser->n_allocated_lines = new_n_lines; +} + +static void +grow_sections (GtkIconThemeFileParser *parser) +{ + int new_n_sections; + + if (parser->n_allocated_sections == 0) + new_n_sections = 1; + else + new_n_sections = parser->n_allocated_sections*2; + + parser->df->sections = g_renew (GtkIconThemeFileSection, + parser->df->sections, + new_n_sections); + parser->n_allocated_sections = new_n_sections; +} + +static gchar * +unescape_string (gchar *str, gint len) +{ + gchar *res; + gchar *p, *q; + gchar *end; + + /* len + 1 is enough, because unescaping never makes the + * string longer */ + res = g_new (gchar, len + 1); + p = str; + q = res; + end = str + len; + + while (p < end) + { + if (*p == 0) + { + /* Found an embedded null */ + g_free (res); + return NULL; + } + if (*p == '\\') + { + p++; + if (p >= end) + { + /* Escape at end of string */ + g_free (res); + return NULL; + } + + switch (*p) + { + case 's': + *q++ = ' '; + break; + case 't': + *q++ = '\t'; + break; + case 'n': + *q++ = '\n'; + break; + case 'r': + *q++ = '\r'; + break; + case '\\': + *q++ = '\\'; + break; + default: + /* Invalid escape code */ + g_free (res); + return NULL; + } + p++; + } + else + *q++ = *p++; + } + *q = 0; + + return res; +} + +static gchar * +escape_string (const gchar *str, gboolean escape_first_space) +{ + gchar *res; + char *q; + const gchar *p; + const gchar *end; + + /* len + 1 is enough, because unescaping never makes the + * string longer */ + res = g_new (gchar, strlen (str)*2 + 1); + + p = str; + q = res; + end = str + strlen (str); + + while (*p) + { + if (*p == ' ') + { + if (escape_first_space && p == str) + { + *q++ = '\\'; + *q++ = 's'; + } + else + *q++ = ' '; + } + else if (*p == '\\') + { + *q++ = '\\'; + *q++ = '\\'; + } + else if (*p == '\t') + { + *q++ = '\\'; + *q++ = 't'; + } + else if (*p == '\n') + { + *q++ = '\\'; + *q++ = 'n'; + } + else if (*p == '\r') + { + *q++ = '\\'; + *q++ = 'r'; + } + else + *q++ = *p; + p++; + } + *q = 0; + + return res; +} + + +static void +open_section (GtkIconThemeFileParser *parser, + const char *name) +{ + int n; + + if (parser->n_allocated_sections == parser->df->n_sections) + grow_sections (parser); + + if (parser->current_section == 0 && + parser->df->sections[0].section_name == 0 && + parser->df->sections[0].n_lines == 0) + { + if (!name) + g_warning ("non-initial NULL section\n"); + + /* The initial section was empty. Piggyback on it. */ + parser->df->sections[0].section_name = g_quark_from_string (name); + + return; + } + + n = parser->df->n_sections++; + + if (name) + parser->df->sections[n].section_name = g_quark_from_string (name); + else + parser->df->sections[n].section_name = 0; + parser->df->sections[n].n_lines = 0; + parser->df->sections[n].lines = NULL; + + parser->current_section = n; + parser->n_allocated_lines = 0; + grow_lines (parser); +} + +static GtkIconThemeFileLine * +new_line (GtkIconThemeFileParser *parser) +{ + GtkIconThemeFileSection *section; + GtkIconThemeFileLine *line; + + section = &parser->df->sections[parser->current_section]; + + if (parser->n_allocated_lines == section->n_lines) + grow_lines (parser); + + line = §ion->lines[section->n_lines++]; + + memset (line, 0, sizeof (GtkIconThemeFileLine)); + + return line; +} + +static gboolean +is_blank_line (GtkIconThemeFileParser *parser) +{ + gchar *p; + + p = parser->line; + + while (*p && *p != '\n') + { + if (!g_ascii_isspace (*p)) + return FALSE; + + p++; + } + return TRUE; +} + +static void +parse_comment_or_blank (GtkIconThemeFileParser *parser) +{ + GtkIconThemeFileLine *line; + gchar *line_end; + + line_end = strchr (parser->line, '\n'); + if (line_end == NULL) + line_end = parser->line + strlen (parser->line); + + line = new_line (parser); + + line->value = g_strndup (parser->line, line_end - parser->line); + + parser->line = (line_end) ? line_end + 1 : NULL; + parser->line_nr++; +} + +static gboolean +parse_section_start (GtkIconThemeFileParser *parser, GError **error) +{ + gchar *line_end; + gchar *section_name; + + line_end = strchr (parser->line, '\n'); + if (line_end == NULL) + line_end = parser->line + strlen (parser->line); + + if (line_end - parser->line <= 2 || + line_end[-1] != ']') + { + report_error (parser, "Invalid syntax for section header", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + section_name = unescape_string (parser->line + 1, line_end - parser->line - 2); + + if (section_name == NULL) + { + report_error (parser, "Invalid escaping in section name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error); + parser_free (parser); + return FALSE; + } + + open_section (parser, section_name); + + parser->line = (line_end) ? line_end + 1 : NULL; + parser->line_nr++; + + g_free (section_name); + + return TRUE; +} + +static gboolean +parse_key_value (GtkIconThemeFileParser *parser, GError **error) +{ + GtkIconThemeFileLine *line; + gchar *line_end; + gchar *key_start; + gchar *key_end; + gchar *key; + gchar *locale_start = NULL; + gchar *locale_end = NULL; + gchar *value_start; + gchar *value; + gchar *p; + + line_end = strchr (parser->line, '\n'); + if (line_end == NULL) + line_end = parser->line + strlen (parser->line); + + p = parser->line; + key_start = p; + while (p < line_end && + (valid[(guchar)*p] & VALID_KEY_CHAR)) + p++; + key_end = p; + + if (key_start == key_end) + { + report_error (parser, "Empty key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + if (p < line_end && *p == '[') + { + p++; + locale_start = p; + while (p < line_end && *p != ']') + p++; + locale_end = p; + + if (p == line_end) + { + report_error (parser, "Unterminated locale specification in key", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + p++; + } + + /* Skip space before '=' */ + while (p < line_end && *p == ' ') + p++; + + if (p < line_end && *p != '=') + { + report_error (parser, "Invalid characters in key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_CHARS, error); + parser_free (parser); + return FALSE; + } + + if (p == line_end) + { + report_error (parser, "No '=' in key/value pair", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + /* Skip the '=' */ + p++; + + /* Skip space after '=' */ + while (p < line_end && *p == ' ') + p++; + + value_start = p; + + value = unescape_string (value_start, line_end - value_start); + if (value == NULL) + { + report_error (parser, "Invalid escaping in value", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error); + parser_free (parser); + return FALSE; + } + + line = new_line (parser); + key = g_strndup (key_start, key_end - key_start); + line->key = g_quark_from_string (key); + g_free (key); + if (locale_start) + line->locale = g_strndup (locale_start, locale_end - locale_start); + line->value = value; + + parser->line = (line_end) ? line_end + 1 : NULL; + parser->line_nr++; + + return TRUE; +} + + +static void +report_error (GtkIconThemeFileParser *parser, + char *message, + GtkIconThemeFileParseError error_code, + GError **error) +{ + GtkIconThemeFileSection *section; + const gchar *section_name = NULL; + + section = &parser->df->sections[parser->current_section]; + + if (section->section_name) + section_name = g_quark_to_string (section->section_name); + + if (error) + { + if (section_name) + *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR, + error_code, + "Error in section %s at line %d: %s", section_name, parser->line_nr, message); + else + *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR, + error_code, + "Error at line %d: %s", parser->line_nr, message); + } +} + + +GtkIconThemeFile * +_gtk_icon_theme_file_new_from_string (char *data, + GError **error) +{ + GtkIconThemeFileParser parser; + + parser.df = g_new0 (GtkIconThemeFile, 1); + parser.current_section = -1; + + parser.n_allocated_lines = 0; + parser.n_allocated_sections = 0; + parser.line_nr = 1; + + parser.line = data; + + /* Put any initial comments in a NULL segment */ + open_section (&parser, NULL); + + while (parser.line && *parser.line) + { + if (*parser.line == '[') { + if (!parse_section_start (&parser, error)) + return NULL; + } else if (is_blank_line (&parser) || + *parser.line == '#') + parse_comment_or_blank (&parser); + else + { + if (!parse_key_value (&parser, error)) + return NULL; + } + } + + return parser.df; +} + +char * +_gtk_icon_theme_file_to_string (GtkIconThemeFile *df) +{ + GtkIconThemeFileSection *section; + GtkIconThemeFileLine *line; + GString *str; + char *s; + int i, j; + + str = g_string_sized_new (800); + + for (i = 0; i < df->n_sections; i ++) + { + section = &df->sections[i]; + + if (section->section_name) + { + g_string_append_c (str, '['); + s = escape_string (g_quark_to_string (section->section_name), FALSE); + g_string_append (str, s); + g_free (s); + g_string_append (str, "]\n"); + } + + for (j = 0; j < section->n_lines; j++) + { + line = §ion->lines[j]; + + if (line->key == 0) + { + g_string_append (str, line->value); + g_string_append_c (str, '\n'); + } + else + { + g_string_append (str, g_quark_to_string (line->key)); + if (line->locale) + { + g_string_append_c (str, '['); + g_string_append (str, line->locale); + g_string_append_c (str, ']'); + } + g_string_append_c (str, '='); + s = escape_string (line->value, TRUE); + g_string_append (str, s); + g_free (s); + g_string_append_c (str, '\n'); + } + } + } + + return g_string_free (str, FALSE); +} + +static GtkIconThemeFileSection * +lookup_section (GtkIconThemeFile *df, + const char *section_name) +{ + GtkIconThemeFileSection *section; + GQuark section_quark; + int i; + + section_quark = g_quark_try_string (section_name); + if (section_quark == 0) + return NULL; + + for (i = 0; i < df->n_sections; i ++) + { + section = &df->sections[i]; + + if (section->section_name == section_quark) + return section; + } + return NULL; +} + +static GtkIconThemeFileLine * +lookup_line (GtkIconThemeFile *df, + GtkIconThemeFileSection *section, + const char *keyname, + const char *locale) +{ + GtkIconThemeFileLine *line; + GQuark key_quark; + int i; + + key_quark = g_quark_try_string (keyname); + if (key_quark == 0) + return NULL; + + for (i = 0; i < section->n_lines; i++) + { + line = §ion->lines[i]; + + if (line->key == key_quark && + ((locale == NULL && line->locale == NULL) || + (locale != NULL && line->locale != NULL && strcmp (locale, line->locale) == 0))) + return line; + } + + return NULL; +} + +gboolean +_gtk_icon_theme_file_get_raw (GtkIconThemeFile *df, + const char *section_name, + const char *keyname, + const char *locale, + char **val) +{ + GtkIconThemeFileSection *section; + GtkIconThemeFileLine *line; + + *val = NULL; + + section = lookup_section (df, section_name); + if (!section) + return FALSE; + + line = lookup_line (df, + section, + keyname, + locale); + + if (!line) + return FALSE; + + *val = g_strdup (line->value); + + return TRUE; +} + + +void +_gtk_icon_theme_file_foreach_section (GtkIconThemeFile *df, + GtkIconThemeFileSectionFunc func, + gpointer user_data) +{ + GtkIconThemeFileSection *section; + int i; + + for (i = 0; i < df->n_sections; i ++) + { + section = &df->sections[i]; + + (*func) (df, g_quark_to_string (section->section_name), user_data); + } + return; +} + +void +_gtk_icon_theme_file_foreach_key (GtkIconThemeFile *df, + const char *section_name, + gboolean include_localized, + GtkIconThemeFileLineFunc func, + gpointer user_data) +{ + GtkIconThemeFileSection *section; + GtkIconThemeFileLine *line; + int i; + + section = lookup_section (df, section_name); + if (!section) + return; + + for (i = 0; i < section->n_lines; i++) + { + line = §ion->lines[i]; + + (*func) (df, g_quark_to_string (line->key), line->locale, line->value, user_data); + } + + return; +} + + +static void +calculate_locale (GtkIconThemeFile *df) +{ + char *p, *lang; + + lang = g_strdup (setlocale (LC_MESSAGES, NULL)); + + if (lang) + { + p = strchr (lang, '.'); + if (p) + *p = '\0'; + p = strchr (lang, '@'); + if (p) + *p = '\0'; + } + else + lang = g_strdup ("C"); + + p = strchr (lang, '_'); + if (p) + { + df->current_locale[0] = g_strdup (lang); + *p = '\0'; + df->current_locale[1] = lang; + } + else + { + df->current_locale[0] = lang; + df->current_locale[1] = NULL; + } +} + +gboolean +_gtk_icon_theme_file_get_locale_string (GtkIconThemeFile *df, + const char *section, + const char *keyname, + char **val) +{ + gboolean res; + + if (df->current_locale[0] == NULL) + calculate_locale (df); + + if (df->current_locale[0] != NULL) + { + res = _gtk_icon_theme_file_get_raw (df,section, keyname, + df->current_locale[0], val); + if (res) + return TRUE; + } + + if (df->current_locale[1] != NULL) + { + res = _gtk_icon_theme_file_get_raw (df,section, keyname, + df->current_locale[1], val); + if (res) + return TRUE; + } + + return _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, val); +} + +gboolean +_gtk_icon_theme_file_get_string (GtkIconThemeFile *df, + const char *section, + const char *keyname, + char **val) +{ + return _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, val); +} + +gboolean +_gtk_icon_theme_file_get_integer (GtkIconThemeFile *df, + const char *section, + const char *keyname, + int *val) +{ + gboolean res; + char *str; + + *val = 0; + + res = _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, &str); + if (!res) + return FALSE; + + + *val = atoi (str); + g_free (str); + + return TRUE; + +} + diff --git a/gtk/gtkiconthemeparser.h b/gtk/gtkiconthemeparser.h new file mode 100644 index 0000000000..aa4bd91742 --- /dev/null +++ b/gtk/gtkiconthemeparser.h @@ -0,0 +1,86 @@ +/* GtkIconThemeParser - a parser of icon-theme files + * gtkiconthemeparser.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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_ICON_THEME_PARSER_H__ +#define __GTK_ICON_THEME_PARSER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GtkIconThemeFile GtkIconThemeFile; + +typedef void (* GtkIconThemeFileSectionFunc) (GtkIconThemeFile *df, + const char *name, + gpointer data); + +/* If @key is %NULL, @value is a comment line. */ +/* @value is raw, unescaped data. */ +typedef void (* GtkIconThemeFileLineFunc) (GtkIconThemeFile *df, + const char *key, + const char *locale, + const char *value, + gpointer data); + +typedef enum +{ + GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, + GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, + GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_CHARS +} GtkIconThemeFileParseError; + +#define GTK_ICON_THEME_FILE_PARSE_ERROR _gtk_icon_theme_file_parse_error_quark() +GQuark _gtk_icon_theme_file_parse_error_quark (void); + +GtkIconThemeFile *_gtk_icon_theme_file_new_from_string (char *data, + GError **error); +char * _gtk_icon_theme_file_to_string (GtkIconThemeFile *df); +void _gtk_icon_theme_file_free (GtkIconThemeFile *df); + +void _gtk_icon_theme_file_foreach_section (GtkIconThemeFile *df, + GtkIconThemeFileSectionFunc func, + gpointer user_data); +void _gtk_icon_theme_file_foreach_key (GtkIconThemeFile *df, + const char *section, + gboolean include_localized, + GtkIconThemeFileLineFunc func, + gpointer user_data); + +/* Gets the raw text of the key, unescaped */ +gboolean _gtk_icon_theme_file_get_raw (GtkIconThemeFile *df, + const char *section, + const char *keyname, + const char *locale, + char **val); +gboolean _gtk_icon_theme_file_get_integer (GtkIconThemeFile *df, + const char *section, + const char *keyname, + int *val); +gboolean _gtk_icon_theme_file_get_string (GtkIconThemeFile *df, + const char *section, + const char *keyname, + char **val); +gboolean _gtk_icon_theme_file_get_locale_string (GtkIconThemeFile *df, + const char *section, + const char *keyname, + char **val); + +G_END_DECLS + +#endif /* GTK_ICON_THEME_PARSER_H */ diff --git a/gtk/gtkrc.c b/gtk/gtkrc.c index 19fea55c20..6048178c56 100644 --- a/gtk/gtkrc.c +++ b/gtk/gtkrc.c @@ -3647,17 +3647,32 @@ gtk_rc_parse_icon_source (GtkRcContext *context, token = g_scanner_get_next_token (scanner); - if (token != G_TOKEN_STRING) + if (token != G_TOKEN_STRING && token != '@') return G_TOKEN_STRING; - source = gtk_icon_source_new (); - - full_filename = gtk_rc_find_pixmap_in_path (context->settings, scanner, scanner->value.v_string); - if (full_filename) + + if (token == G_TOKEN_STRING) { - gtk_icon_source_set_filename (source, full_filename); - g_free (full_filename); + /* Filename */ + + full_filename = gtk_rc_find_pixmap_in_path (context->settings, scanner, scanner->value.v_string); + if (full_filename) + { + gtk_icon_source_set_filename (source, full_filename); + g_free (full_filename); + } + } + else + { + /* Icon name */ + + token = g_scanner_get_next_token (scanner); + + if (token != G_TOKEN_STRING) + return G_TOKEN_STRING; + + gtk_icon_source_set_icon_name (source, scanner->value.v_string); } /* We continue parsing even if we didn't find the pixmap so that rest of the @@ -3793,7 +3808,8 @@ gtk_rc_parse_icon_source (GtkRcContext *context, } done: - if (gtk_icon_source_get_filename (source)) + if (gtk_icon_source_get_filename (source) || + gtk_icon_source_get_icon_name (source)) { gtk_icon_set_add_source (icon_set, source); *icon_set_valid = TRUE; diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index 2ffc3f0516..bb058a7174 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -49,6 +49,7 @@ enum { PROP_CURSOR_BLINK_TIME, PROP_SPLIT_CURSOR, PROP_THEME_NAME, + PROP_ICON_THEME_NAME, PROP_KEY_THEME_NAME, PROP_MENU_BAR_ACCEL, PROP_DND_DRAG_THRESHOLD, @@ -203,6 +204,14 @@ gtk_settings_class_init (GtkSettingsClass *class) G_PARAM_READWRITE), NULL); g_assert (result == PROP_THEME_NAME); + result = settings_install_property_parser (class, + g_param_spec_string ("gtk-icon-theme-name", + _("Icon Theme Name"), + _("Name of icon theme to use"), + "hicolor", + G_PARAM_READWRITE), + NULL); + g_assert (result == PROP_ICON_THEME_NAME); result = settings_install_property_parser (class, g_param_spec_string ("gtk-key-theme-name", _("Key Theme Name"), diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index ea60d51452..411bb78737 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -4623,7 +4623,7 @@ gtk_widget_peek_pango_context (GtkWidget *widget) * * If you create and keep a #PangoLayout using this context, you must * deal with changes to the context by calling pango_layout_context_changed() - * on the layout in response to the ::style_set and ::direction_set signals + * on the layout in response to the ::style-set and ::direction-changed signals * for the widget. * * Return value: the #PangoContext for the widget. @@ -4699,7 +4699,7 @@ gtk_widget_create_pango_context (GtkWidget *widget) * If you keep a #PangoLayout created in this way around, in order * notify the layout of changes to the base direction or font of this * widget, you must call pango_layout_context_changed() in response to - * the ::style_set and ::direction_set signals for the widget. + * the ::style-set and ::direction-changed signals for the widget. * * Return value: the new #PangoLayout **/