diff options
-rw-r--r-- | ChangeLog | 40 | ||||
-rw-r--r-- | ChangeLog.pre-2-10 | 40 | ||||
-rw-r--r-- | ChangeLog.pre-2-4 | 40 | ||||
-rw-r--r-- | ChangeLog.pre-2-6 | 40 | ||||
-rw-r--r-- | ChangeLog.pre-2-8 | 40 | ||||
-rw-r--r-- | gdk/x11/gdkevents-x11.c | 3 | ||||
-rw-r--r-- | gtk/Makefile.am | 5 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkiconfactory.c | 1249 | ||||
-rw-r--r-- | gtk/gtkiconfactory.h | 9 | ||||
-rw-r--r-- | gtk/gtkicontheme.c | 2535 | ||||
-rw-r--r-- | gtk/gtkicontheme.h | 161 | ||||
-rw-r--r-- | gtk/gtkiconthemeparser.c | 854 | ||||
-rw-r--r-- | gtk/gtkiconthemeparser.h | 86 | ||||
-rw-r--r-- | gtk/gtkrc.c | 32 | ||||
-rw-r--r-- | gtk/gtksettings.c | 9 | ||||
-rw-r--r-- | gtk/gtkwidget.c | 4 |
17 files changed, 4545 insertions, 603 deletions
@@ -1,3 +1,43 @@ +Wed Jul 2 18:00:56 2003 Owen Taylor <otaylor@redhat.com> + + * 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 <otaylor@redhat.com> * 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 <otaylor@redhat.com> + + * 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 <otaylor@redhat.com> * 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 <otaylor@redhat.com> + + * 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 <otaylor@redhat.com> * 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 <otaylor@redhat.com> + + * 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 <otaylor@redhat.com> * 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 <otaylor@redhat.com> + + * 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 <otaylor@redhat.com> * 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 \ @@ -82,6 +82,7 @@ #include <gtk/gtkhscrollbar.h> #include <gtk/gtkhseparator.h> #include <gtk/gtkiconfactory.h> +#include <gtk/gtkicontheme.h> #include <gtk/gtkimage.h> #include <gtk/gtkimagemenuitem.h> #include <gtk/gtkimcontext.h> 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); - - gtk_icon_set_add_source (set, source); - - g_object_unref (source->pixbuf); -} - -#if 0 -static GtkIconSet * -sized_icon_set_from_inline (const guchar *inline_data, - GtkIconSize size) -{ - GtkIconSet *set; - - GtkIconSource source = { NULL, NULL, 0, 0, 0, - TRUE, TRUE, FALSE }; - - source.size = size; - - set = gtk_icon_set_new (); + GtkIconSet *set = gtk_icon_set_new (); + GtkIconSource source = GTK_ICON_SOURCE_INIT (TRUE, TRUE, TRUE); - add_source (set, source, inline_data); + source.type = GTK_ICON_SOURCE_ICON_NAME; + source.source.icon_name = (gchar *)stock_id; + gtk_icon_set_add_source (set, &source); - return set; + gtk_icon_factory_add (factory, stock_id, set); + gtk_icon_set_unref (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) +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 }; - - 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); - - if (inline_data_rtl != NULL) - { - source.direction = GTK_TEXT_DIR_RTL; - add_source (set, &source, inline_data_rtl); - } + gtk_icon_set_add_source (set, &source); - 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); - } + 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); - return set; + gtk_icon_factory_add (factory, stock_id, set); + gtk_icon_set_unref (set); } -static GtkIconSet * -unsized_icon_set_from_inline (const guchar *inline_data) +static void +add_default_image (const gchar *stock_id, + gint size, + 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 (); + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_inline (-1, inline_data, FALSE, NULL); + g_assert (pixbuf); - add_source (set, &source, inline_data); + gtk_icon_theme_add_builtin_icon (stock_id, size, pixbuf); - return set; + g_object_unref (pixbuf); } -#if 0 static void -add_sized (GtkIconFactory *factory, - const guchar *inline_data, - GtkIconSize size, - const gchar *stock_id) +add_icon (GtkIconFactory *factory, + const gchar *stock_id, + gint size, + const guchar *inline_data) { - GtkIconSet *set; - - set = sized_icon_set_from_inline (inline_data, size); - - 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); } -#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) +add_icon2 (GtkIconFactory *factory, + const gchar *stock_id, + gint size1, + const guchar *inline_data1, + gint size2, + const guchar *inline_data2) { - GtkIconSet *set; - - set = sized_with_fallback_icon_set_from_inline (fallback_data_ltr, fallback_data_rtl, - inline_data_ltr, inline_data_rtl, - size); + register_stock_icon (factory, stock_id); - 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_sized_with_fallback_and_rtl (factory, - fallback_data, NULL, - inline_data, NULL, - size, stock_id); + add_default_image (stock_id, size1, inline_data1); + add_default_image (stock_id, size2, inline_data2); } static void -add_unsized (GtkIconFactory *factory, - const guchar *inline_data, - const gchar *stock_id) +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) { - GtkIconSet *set; + gchar *stock_id_ltr = g_strconcat (stock_id, "-ltr", NULL); + gchar *stock_id_rtl = g_strconcat (stock_id, "-rtl", NULL); - set = unsized_icon_set_from_inline (inline_data); + register_bidi_stock_icon (factory, stock_id, + stock_id_ltr, stock_id_rtl); - gtk_icon_factory_add (factory, stock_id, set); + add_default_image (stock_id_ltr, size1, inline_data_ltr1); + add_default_image (stock_id_ltr, size2, inline_data_ltr2); - gtk_icon_set_unref (set); + 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_sized_with_fallback (factory, - stock_align_center_24, - stock_align_center_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_CENTER); - - add_sized_with_fallback (factory, - stock_align_justify_24, - stock_align_justify_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_FILL); - - add_sized_with_fallback (factory, - stock_align_left_24, - stock_align_left_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_LEFT); - - add_sized_with_fallback (factory, - stock_align_right_24, - stock_align_right_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_JUSTIFY_RIGHT); - - add_sized_with_fallback (factory, - stock_bottom_24, - stock_bottom_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_BOTTOM); - - add_sized_with_fallback (factory, - stock_cdrom_24, - stock_cdrom_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CDROM); - - add_sized_with_fallback (factory, - stock_convert_24, - stock_convert_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CONVERT); - - add_sized_with_fallback (factory, - stock_copy_24, - stock_copy_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_COPY); - - add_sized_with_fallback (factory, - stock_cut_24, - stock_cut_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_CUT); - - add_sized_with_fallback (factory, - stock_down_arrow_24, - stock_down_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_DOWN); - - add_sized_with_fallback (factory, - stock_exec_24, - stock_exec_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_EXECUTE); - - add_sized_with_fallback (factory, - stock_exit_24, - stock_exit_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_QUIT); - - 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_sized_with_fallback (factory, - stock_font_24, - stock_font_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SELECT_FONT); - - add_sized_with_fallback (factory, - stock_help_24, - stock_help_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_HELP); - - add_sized_with_fallback (factory, - stock_home_24, - stock_home_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_HOME); - - 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_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_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_sized_with_fallback (factory, - stock_missing_image_24, - stock_missing_image_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_MISSING_IMAGE); - - add_sized_with_fallback (factory, - stock_new_24, - stock_new_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_NEW); - - add_sized_with_fallback (factory, - stock_open_24, - stock_open_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_OPEN); - - add_sized_with_fallback (factory, - stock_paste_24, - stock_paste_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PASTE); - - add_sized_with_fallback (factory, - stock_preferences_24, - stock_preferences_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_PREFERENCES); - - 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_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_sized_with_fallback (factory, - stock_remove_24, - stock_remove_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REMOVE); - - add_sized_with_fallback (factory, - stock_refresh_24, - stock_refresh_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_REFRESH); - - 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_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_sized_with_fallback (factory, - stock_save_24, - stock_save_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SAVE); - - add_sized_with_fallback (factory, - stock_save_24, - stock_save_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FLOPPY); - - add_sized_with_fallback (factory, - stock_save_as_24, - stock_save_as_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SAVE_AS); - - add_sized_with_fallback (factory, - stock_search_24, - stock_search_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FIND); - - add_sized_with_fallback (factory, - stock_search_replace_24, - stock_search_replace_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_FIND_AND_REPLACE); - - add_sized_with_fallback (factory, - stock_sort_descending_24, - stock_sort_descending_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SORT_DESCENDING); - - add_sized_with_fallback (factory, - stock_sort_ascending_24, - stock_sort_ascending_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SORT_ASCENDING); - - add_sized_with_fallback (factory, - stock_spellcheck_24, - stock_spellcheck_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_SPELL_CHECK); - - add_sized_with_fallback (factory, - stock_stop_24, - stock_stop_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_STOP); - - add_sized_with_fallback (factory, - stock_text_bold_24, - stock_text_bold_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_BOLD); - - add_sized_with_fallback (factory, - stock_text_italic_24, - stock_text_italic_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_ITALIC); - - add_sized_with_fallback (factory, - stock_text_strikethrough_24, - stock_text_strikethrough_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_STRIKETHROUGH); - - add_sized_with_fallback (factory, - stock_text_underline_24, - stock_text_underline_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_UNDERLINE); - - add_sized_with_fallback (factory, - stock_top_24, - stock_top_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GOTO_TOP); - - add_sized_with_fallback (factory, - stock_trash_24, - stock_trash_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_DELETE); - - 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_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_sized_with_fallback (factory, - stock_up_arrow_24, - stock_up_arrow_16, - GTK_ICON_SIZE_MENU, - GTK_STOCK_GO_UP); - -/* 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_icon2 (factory, GTK_STOCK_ADD, + 16, stock_add_16, + 24, stock_add_24); + + add_icon2 (factory, GTK_STOCK_JUSTIFY_CENTER, + 16, stock_align_center_16, + 24, stock_align_center_24); + + add_icon2 (factory, GTK_STOCK_JUSTIFY_FILL, + 16, stock_align_justify_16, + 24, stock_align_justify_24); + + 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_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_icon2 (factory, GTK_STOCK_CONVERT, + 16, stock_convert_16, + 24, stock_convert_24); + + add_icon2 (factory, GTK_STOCK_COPY, + 16, stock_copy_16, + 24, stock_copy_24); + + add_icon2 (factory, GTK_STOCK_CUT, + 16, stock_cut_16, + 24, stock_cut_24); + + add_icon2 (factory, GTK_STOCK_GO_DOWN, + 16, stock_down_arrow_16, + 24, stock_down_arrow_24); + + add_icon2 (factory, GTK_STOCK_EXECUTE, + 16, stock_exec_16, + 24, stock_exec_24); + + add_icon2 (factory, GTK_STOCK_QUIT, + 16, stock_exit_16, + 24, stock_exit_24); + + add_icon_bidi2 (factory, GTK_STOCK_GOTO_FIRST, + 16, stock_first_16, stock_last_16, + 24, stock_first_24, stock_last_24); + + add_icon2 (factory, GTK_STOCK_SELECT_FONT, + 16, stock_font_16, + 24, stock_font_24); + + add_icon2 (factory, GTK_STOCK_HELP, + 16, stock_help_16, + 24, stock_help_24); + + add_icon2 (factory, GTK_STOCK_HOME, + 16, stock_home_16, + 24, stock_home_24); + + 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_icon_bidi2 (factory, GTK_STOCK_GOTO_LAST, + 16, stock_last_16, stock_first_16, + 24, stock_last_24, stock_first_24); + + 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_icon2 (factory, GTK_STOCK_MISSING_IMAGE, + 16, stock_missing_image_16, + 24, stock_missing_image_24); + + add_icon2 (factory, GTK_STOCK_NEW, + 16, stock_new_16, + 24, stock_new_24); + + add_icon2 (factory, GTK_STOCK_OPEN, + 16, stock_open_16, + 24, stock_open_24); + + add_icon2 (factory, GTK_STOCK_PASTE, + 16, stock_paste_16, + 24, stock_paste_24); + + add_icon2 (factory, GTK_STOCK_PREFERENCES, + 16, stock_preferences_16, + 24, stock_preferences_24); + + add_icon2 (factory, GTK_STOCK_PRINT, + 16, stock_print_16, + 24, stock_print_24); + + add_icon2 (factory, GTK_STOCK_PRINT_PREVIEW, + 16, stock_print_preview_16, + 24, stock_print_preview_24); + + add_icon2 (factory, GTK_STOCK_PROPERTIES, + 16, stock_properties_16, + 24, stock_properties_24); + + add_icon_bidi2 (factory, GTK_STOCK_REDO, + 16, stock_redo_16, stock_redo_rtl_16, + 24, stock_redo_24, stock_redo_rtl_24); + + add_icon2 (factory, GTK_STOCK_REMOVE, + 16, stock_remove_16, + 24, stock_remove_24); + + add_icon2 (factory, GTK_STOCK_REFRESH, + 16, stock_refresh_16, + 24, stock_refresh_24); + + 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_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_icon2 (factory, GTK_STOCK_SAVE, + 16, stock_save_16, + 24, stock_save_24); + + add_icon2 (factory, GTK_STOCK_FLOPPY, + 16, stock_save_16, + 24, stock_save_24); + + add_icon2 (factory, GTK_STOCK_SAVE_AS, + 16, stock_save_as_16, + 24, stock_save_as_24); + + add_icon2 (factory, GTK_STOCK_FIND, + 16, stock_search_16, + 24, stock_search_24); + + add_icon2 (factory, GTK_STOCK_FIND_AND_REPLACE, + 16, stock_search_replace_16, + 24, stock_search_replace_24); + + add_icon2 (factory, GTK_STOCK_SORT_DESCENDING, + 16, stock_sort_descending_16, + 24, stock_sort_descending_24); + + add_icon2 (factory, GTK_STOCK_SORT_ASCENDING, + 16, stock_sort_ascending_16, + 24, stock_sort_ascending_24); + + add_icon2 (factory, GTK_STOCK_SPELL_CHECK, + 16, stock_spellcheck_16, + 24, stock_spellcheck_24); + + add_icon2 (factory, GTK_STOCK_STOP, + 16, stock_stop_16, + 24, stock_stop_24); + + add_icon2 (factory, GTK_STOCK_BOLD, + 16, stock_text_bold_16, + 24, stock_text_bold_24); + + add_icon2 (factory, GTK_STOCK_ITALIC, + 16, stock_text_italic_16, + 24, stock_text_italic_24); + + add_icon2 (factory, GTK_STOCK_STRIKETHROUGH, + 16, stock_text_strikethrough_16, + 24, stock_text_strikethrough_24); + + add_icon2 (factory, GTK_STOCK_UNDERLINE, + 16, stock_text_underline_16, + 24, stock_text_underline_24); + + add_icon2 (factory, GTK_STOCK_GOTO_TOP, + 16, stock_top_16, + 24, stock_top_24); + + add_icon2 (factory, GTK_STOCK_DELETE, + 16, stock_trash_16, + 24, stock_trash_24); + + add_icon_bidi2 (factory, GTK_STOCK_UNDELETE, + 16, stock_undelete_16, stock_undelete_rtl_16, + 24, stock_undelete_24, stock_undelete_rtl_24); + + add_icon_bidi2 (factory, GTK_STOCK_UNDO, + 16, stock_undo_16, stock_undo_rtl_16, + 24, stock_undo_24, stock_undo_rtl_24); + + add_icon2 (factory, GTK_STOCK_GO_UP, + 16, stock_up_arrow_16, + 24, stock_up_arrow_24); + + /* Generic size only */ + + 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 <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> + +#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 <glib-object.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdkscreen.h> + +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 <string.h> +#include <locale.h> +#include <stdlib.h> + +#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 <glib.h> + +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) + { + /* 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 { - gtk_icon_source_set_filename (source, full_filename); - g_free (full_filename); + /* 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, @@ -204,6 +205,14 @@ gtk_settings_class_init (GtkSettingsClass *class) 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"), _("Name of key theme RC file to load"), 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 **/ |