1 /* RoxIconTheme - a loader for icon themes
2 * gtk-icon-theme.c Copyright (C) 2002, 2003 Red Hat, Inc.
4 * This was LGPL; it's now GPL, as allowed by the LGPL. It's also very
5 * stripped down. GTK 2.4 will have this stuff built-in.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
15 #include <sys/types.h>
26 #define S_ISDIR(mode) ((mode)&_S_IFDIR)
28 #endif /* G_OS_WIN32 */
30 #include "gui_support.h"
31 #include "gtkicontheme.h"
32 #include "gtkiconthemeparser.h"
33 /* #include "gtkintl.h" */
34 #include <gtk/gtksettings.h>
35 #include <gtk/gtkprivate.h>
37 #define DEFAULT_THEME_NAME "hicolor"
39 static GdkPixbuf
*rox_icon_info_load_icon(RoxIconInfo
*icon_info
,
42 typedef struct _GtkIconData GtkIconData
;
47 ICON_THEME_DIR_SCALABLE
,
48 ICON_THEME_DIR_THRESHOLD
,
49 ICON_THEME_DIR_UNTHEMED
52 /* In reverse search order: */
56 ICON_SUFFIX_XPM
= 1 << 0,
57 ICON_SUFFIX_SVG
= 1 << 1,
58 ICON_SUFFIX_PNG
= 1 << 2,
61 struct _RoxIconThemePrivate
63 guint custom_theme
: 1;
64 guint pixbuf_supports_svg
: 1;
70 gboolean themes_valid
;
71 /* A list of all the themes needed to look up icons.
72 * In search order, without duplicates
75 GHashTable
*unthemed_icons
;
77 /* Note: The keys of this hashtable are owned by the
78 * themedir and unthemed hashtables.
80 GHashTable
*all_icons
;
82 /* time when we last stat:ed for theme changes */
91 /* Information about the source
94 GdkPixbuf
*builtin_pixbuf
;
98 /* Information about the directory where
99 * the source was found
101 IconThemeDirType dir_type
;
105 /* Parameters influencing the scaled icon
108 gboolean raw_coordinates
;
110 /* Cached information if we go ahead and try to load
125 /* In search order */
131 gboolean has_embedded_rect
;
134 GdkPoint
*attach_points
;
135 gint n_attach_points
;
142 IconThemeDirType type
;
153 GHashTable
*icon_data
;
159 char *no_svg_filename
;
171 time_t mtime
; /* 0 == not existing or not a dir */
174 static void rox_icon_theme_class_init (RoxIconThemeClass
*klass
);
175 static void rox_icon_theme_init (RoxIconTheme
*icon_theme
);
176 static void rox_icon_theme_finalize (GObject
*object
);
177 static void theme_dir_destroy (IconThemeDir
*dir
);
179 static void theme_destroy (IconTheme
*theme
);
180 static RoxIconInfo
*theme_lookup_icon (IconTheme
*theme
,
181 const char *icon_name
,
184 gboolean use_default_icons
);
185 static void theme_subdir_load (RoxIconTheme
*icon_theme
,
187 GtkIconThemeFile
*theme_file
,
189 static void do_theme_change (RoxIconTheme
*icon_theme
);
191 static void blow_themes (RoxIconTheme
*icon_themes
);
193 static void icon_data_free (GtkIconData
*icon_data
);
195 static RoxIconInfo
*icon_info_new (void);
196 static RoxIconInfo
*icon_info_new_builtin (BuiltinIcon
*icon
);
198 static IconSuffix
suffix_from_name (const char *name
);
200 static BuiltinIcon
*find_builtin_icon (const gchar
*icon_name
,
202 gint
*min_difference_p
,
203 gboolean
*has_larger_p
);
205 static guint signal_changed
= 0;
207 static GHashTable
*icon_theme_builtin_icons
;
210 rox_icon_theme_get_type (void)
212 static GType type
= 0;
216 static const GTypeInfo info
=
218 sizeof (RoxIconThemeClass
),
219 NULL
, /* base_init */
220 NULL
, /* base_finalize */
221 (GClassInitFunc
) rox_icon_theme_class_init
,
222 NULL
, /* class_finalize */
223 NULL
, /* class_data */
224 sizeof (RoxIconTheme
),
226 (GInstanceInitFunc
) rox_icon_theme_init
,
229 type
= g_type_register_static (G_TYPE_OBJECT
, "RoxIconTheme", &info
, 0);
236 rox_icon_theme_new (void)
238 return g_object_new (ROX_TYPE_ICON_THEME
, NULL
);
242 rox_icon_theme_class_init (RoxIconThemeClass
*klass
)
244 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
246 gobject_class
->finalize
= rox_icon_theme_finalize
;
249 * RoxIconTheme::changed
250 * @icon_theme: the icon theme
252 * Emitted when the current icon theme is switched or GTK+ detects
253 * that a change has occurred in the contents of the current
256 signal_changed
= g_signal_new ("changed",
257 G_TYPE_FROM_CLASS (klass
),
259 G_STRUCT_OFFSET (RoxIconThemeClass
, changed
),
261 g_cclosure_marshal_VOID__VOID
,
264 /* g_type_class_add_private (klass, sizeof (RoxIconThemePrivate)); */
268 update_current_theme (RoxIconTheme
*icon_theme
)
270 RoxIconThemePrivate
*priv
= icon_theme
->priv
;
272 if (!priv
->custom_theme
)
277 theme
= g_strdup (DEFAULT_THEME_NAME
);
279 if (strcmp (priv
->current_theme
, theme
) != 0)
281 g_free (priv
->current_theme
);
282 priv
->current_theme
= theme
;
284 do_theme_change (icon_theme
);
292 pixbuf_supports_svg ()
294 GSList
*formats
= gdk_pixbuf_get_formats ();
296 gboolean found_svg
= FALSE
;
298 for (tmp_list
= formats
; tmp_list
&& !found_svg
; tmp_list
= tmp_list
->next
)
300 gchar
**mime_types
= gdk_pixbuf_format_get_mime_types (tmp_list
->data
);
303 for (mime_type
= mime_types
; *mime_type
&& !found_svg
; mime_type
++)
305 if (strcmp (*mime_type
, "image/svg") == 0)
309 g_strfreev (mime_types
);
312 g_slist_free (formats
);
318 rox_icon_theme_init (RoxIconTheme
*icon_theme
)
320 RoxIconThemePrivate
*priv
;
322 priv
= g_new0(RoxIconThemePrivate
, 1);
323 icon_theme
->priv
= priv
;
325 priv
->custom_theme
= FALSE
;
326 priv
->current_theme
= g_strdup (DEFAULT_THEME_NAME
);
327 priv
->search_path
= g_new (char *, 5);
330 priv
->search_path
[0] = g_build_filename (g_get_home_dir (), ".icons", NULL
);
331 priv
->search_path
[1] = g_strdup ("/usr/local/share/icons");
332 priv
->search_path
[2] = g_strdup ("/usr/local/share/pixmaps");
333 priv
->search_path
[3] = g_strdup ("/usr/share/icons");
334 priv
->search_path
[4] = g_strdup ("/usr/share/pixmaps");
335 priv
->search_path_len
= 5;
337 priv
->themes_valid
= FALSE
;
339 priv
->unthemed_icons
= NULL
;
341 priv
->pixbuf_supports_svg
= pixbuf_supports_svg ();
345 free_dir_mtime (IconThemeDirMtime
*dir_mtime
)
347 g_free (dir_mtime
->dir
);
352 do_theme_change (RoxIconTheme
*icon_theme
)
354 blow_themes (icon_theme
);
355 g_signal_emit (G_OBJECT (icon_theme
), signal_changed
, 0);
359 blow_themes (RoxIconTheme
*icon_theme
)
361 RoxIconThemePrivate
*priv
= icon_theme
->priv
;
363 if (priv
->themes_valid
)
365 g_hash_table_destroy (priv
->all_icons
);
366 g_list_foreach (priv
->themes
, (GFunc
)theme_destroy
, NULL
);
367 g_list_free (priv
->themes
);
368 g_list_foreach (priv
->dir_mtimes
, (GFunc
)free_dir_mtime
, NULL
);
369 g_list_free (priv
->dir_mtimes
);
370 g_hash_table_destroy (priv
->unthemed_icons
);
373 priv
->unthemed_icons
= NULL
;
374 priv
->dir_mtimes
= NULL
;
375 priv
->all_icons
= NULL
;
376 priv
->themes_valid
= FALSE
;
380 rox_icon_theme_finalize (GObject
*object
)
382 RoxIconTheme
*icon_theme
;
383 RoxIconThemePrivate
*priv
;
386 icon_theme
= ROX_ICON_THEME (object
);
387 priv
= icon_theme
->priv
;
389 g_free (priv
->current_theme
);
390 priv
->current_theme
= NULL
;
392 for (i
=0; i
< priv
->search_path_len
; i
++)
393 g_free (priv
->search_path
[i
]);
395 g_free (priv
->search_path
);
396 priv
->search_path
= NULL
;
398 blow_themes (icon_theme
);
402 rox_icon_theme_get_search_path (RoxIconTheme
*icon_theme
,
406 RoxIconThemePrivate
*priv
;
409 g_return_if_fail (ROX_IS_ICON_THEME (icon_theme
));
411 priv
= icon_theme
->priv
;
414 *n_elements
= priv
->search_path_len
;
418 *path
= g_new (gchar
*, priv
->search_path_len
+ 1);
419 for (i
= 0; i
< priv
->search_path_len
; i
++)
420 (*path
)[i
] = g_strdup (priv
->search_path
[i
]); /* (was +1) */
426 rox_icon_theme_set_custom_theme (RoxIconTheme
*icon_theme
,
427 const gchar
*theme_name
)
429 RoxIconThemePrivate
*priv
;
431 g_return_if_fail (ROX_IS_ICON_THEME (icon_theme
));
433 priv
= icon_theme
->priv
;
435 if (theme_name
!= NULL
)
437 priv
->custom_theme
= TRUE
;
438 if (strcmp (theme_name
, priv
->current_theme
) != 0)
440 g_free (priv
->current_theme
);
441 priv
->current_theme
= g_strdup (theme_name
);
443 do_theme_change (icon_theme
);
448 priv
->custom_theme
= FALSE
;
450 update_current_theme (icon_theme
);
455 insert_theme (RoxIconTheme
*icon_theme
, const char *theme_name
)
461 RoxIconThemePrivate
*priv
;
467 GtkIconThemeFile
*theme_file
;
468 IconThemeDirMtime
*dir_mtime
;
469 struct stat stat_buf
;
471 priv
= icon_theme
->priv
;
473 for (l
= priv
->themes
; l
!= NULL
; l
= l
->next
)
476 if (strcmp (theme
->name
, theme_name
) == 0)
480 for (i
= 0; i
< priv
->search_path_len
; i
++)
482 path
= g_build_filename (priv
->search_path
[i
],
485 dir_mtime
= g_new (IconThemeDirMtime
, 1);
486 dir_mtime
->dir
= path
;
487 if (stat (path
, &stat_buf
) == 0 && S_ISDIR (stat_buf
.st_mode
))
488 dir_mtime
->mtime
= stat_buf
.st_mtime
;
490 dir_mtime
->mtime
= 0;
492 priv
->dir_mtimes
= g_list_prepend (priv
->dir_mtimes
, dir_mtime
);
496 for (i
= 0; i
< priv
->search_path_len
; i
++)
498 path
= g_build_filename (priv
->search_path
[i
],
502 if (g_file_test (path
, G_FILE_TEST_IS_REGULAR
)) {
503 if (g_file_get_contents (path
, &contents
, NULL
, NULL
)) {
504 theme_file
= _rox_icon_theme_file_new_from_string (contents
, NULL
);
513 if (theme_file
== NULL
)
516 theme
= g_new (IconTheme
, 1);
517 if (!_rox_icon_theme_file_get_locale_string (theme_file
,
520 &theme
->display_name
))
522 g_warning ("Theme file for %s has no name\n", theme_name
);
524 _rox_icon_theme_file_free (theme_file
);
528 if (!_rox_icon_theme_file_get_string (theme_file
,
533 g_warning ("Theme file for %s has no directories\n", theme_name
);
534 g_free (theme
->display_name
);
536 _rox_icon_theme_file_free (theme_file
);
540 theme
->name
= g_strdup (theme_name
);
541 _rox_icon_theme_file_get_locale_string (theme_file
,
545 _rox_icon_theme_file_get_string (theme_file
,
550 dirs
= g_strsplit (directories
, ",", 0);
553 for (i
= 0; dirs
[i
] != NULL
; i
++)
554 theme_subdir_load (icon_theme
, theme
, theme_file
, dirs
[i
]);
558 theme
->dirs
= g_list_reverse (theme
->dirs
);
560 g_free (directories
);
562 /* Prepend the finished theme */
563 priv
->themes
= g_list_prepend (priv
->themes
, theme
);
565 if (_rox_icon_theme_file_get_string (theme_file
,
570 themes
= g_strsplit (inherits
, ",", 0);
572 for (i
= 0; themes
[i
] != NULL
; i
++)
573 insert_theme (icon_theme
, themes
[i
]);
580 _rox_icon_theme_file_free (theme_file
);
584 free_unthemed_icon (UnthemedIcon
*unthemed_icon
)
586 if (unthemed_icon
->svg_filename
)
587 g_free (unthemed_icon
->svg_filename
);
588 if (unthemed_icon
->svg_filename
)
589 g_free (unthemed_icon
->no_svg_filename
);
590 g_free (unthemed_icon
);
594 load_themes (RoxIconTheme
*icon_theme
)
596 RoxIconThemePrivate
*priv
;
599 char *dir
, *base_name
, *dot
;
602 UnthemedIcon
*unthemed_icon
;
603 IconSuffix old_suffix
, new_suffix
;
606 priv
= icon_theme
->priv
;
608 priv
->all_icons
= g_hash_table_new (g_str_hash
, g_str_equal
);
610 insert_theme (icon_theme
, priv
->current_theme
);
612 /* Always look in the "default" icon theme */
613 insert_theme (icon_theme
, DEFAULT_THEME_NAME
);
614 priv
->themes
= g_list_reverse (priv
->themes
);
616 priv
->unthemed_icons
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
617 g_free
, (GDestroyNotify
)free_unthemed_icon
);
619 for (base
= 0; base
< icon_theme
->priv
->search_path_len
; base
++)
621 dir
= icon_theme
->priv
->search_path
[base
];
622 gdir
= g_dir_open (dir
, 0, NULL
);
627 while ((file
= g_dir_read_name (gdir
)))
629 new_suffix
= suffix_from_name (file
);
631 if (new_suffix
!= ICON_SUFFIX_NONE
)
633 abs_file
= g_build_filename (dir
, file
, NULL
);
635 base_name
= g_strdup (file
);
637 dot
= strrchr (base_name
, '.');
641 if ((unthemed_icon
= g_hash_table_lookup (priv
->unthemed_icons
,
644 if (new_suffix
== ICON_SUFFIX_SVG
)
646 if (unthemed_icon
->no_svg_filename
)
649 unthemed_icon
->svg_filename
= abs_file
;
653 if (unthemed_icon
->no_svg_filename
)
655 old_suffix
= suffix_from_name (unthemed_icon
->no_svg_filename
);
656 if (new_suffix
> old_suffix
)
658 g_free (unthemed_icon
->no_svg_filename
);
659 unthemed_icon
->no_svg_filename
= abs_file
;
665 unthemed_icon
->no_svg_filename
= abs_file
;
672 unthemed_icon
= g_new0 (UnthemedIcon
, 1);
674 if (new_suffix
== ICON_SUFFIX_SVG
)
675 unthemed_icon
->svg_filename
= abs_file
;
677 unthemed_icon
->svg_filename
= abs_file
;
679 g_hash_table_insert (priv
->unthemed_icons
,
682 g_hash_table_insert (priv
->all_icons
,
690 priv
->themes_valid
= TRUE
;
692 g_get_current_time(&tv
);
693 priv
->last_stat_time
= tv
.tv_sec
;
697 ensure_valid_themes (RoxIconTheme
*icon_theme
)
699 RoxIconThemePrivate
*priv
= icon_theme
->priv
;
702 if (priv
->themes_valid
)
704 g_get_current_time(&tv
);
706 if (ABS (tv
.tv_sec
- priv
->last_stat_time
) > 5)
707 rox_icon_theme_rescan_if_needed (icon_theme
);
710 if (!priv
->themes_valid
)
711 load_themes (icon_theme
);
715 rox_icon_theme_lookup_icon (RoxIconTheme
*icon_theme
,
716 const gchar
*icon_name
,
718 RoxIconLookupFlags flags
)
720 RoxIconThemePrivate
*priv
;
722 RoxIconInfo
*icon_info
= NULL
;
723 UnthemedIcon
*unthemed_icon
;
725 gboolean use_builtin
;
726 gboolean found_default
;
728 g_return_val_if_fail (ROX_IS_ICON_THEME (icon_theme
), NULL
);
729 g_return_val_if_fail (icon_name
!= NULL
, NULL
);
730 g_return_val_if_fail ((flags
& ROX_ICON_LOOKUP_NO_SVG
) == 0 ||
731 (flags
& ROX_ICON_LOOKUP_FORCE_SVG
) == 0, NULL
);
733 priv
= icon_theme
->priv
;
735 if (flags
& ROX_ICON_LOOKUP_NO_SVG
)
737 else if (flags
& ROX_ICON_LOOKUP_FORCE_SVG
)
740 allow_svg
= priv
->pixbuf_supports_svg
;
742 use_builtin
= (flags
& ROX_ICON_LOOKUP_USE_BUILTIN
);
744 ensure_valid_themes (icon_theme
);
746 found_default
= FALSE
;
750 IconTheme
*icon_theme
= l
->data
;
752 if (strcmp (icon_theme
->name
, DEFAULT_THEME_NAME
) == 0)
753 found_default
= TRUE
;
755 icon_info
= theme_lookup_icon (icon_theme
, icon_name
, size
, allow_svg
, use_builtin
);
764 BuiltinIcon
*builtin
= find_builtin_icon (icon_name
, size
, NULL
, NULL
);
767 icon_info
= icon_info_new_builtin (builtin
);
772 unthemed_icon
= g_hash_table_lookup (priv
->unthemed_icons
, icon_name
);
775 icon_info
= icon_info_new ();
777 /* A SVG icon, when allowed, beats out a XPM icon, but not
781 unthemed_icon
->svg_filename
&&
782 (!unthemed_icon
->no_svg_filename
||
783 suffix_from_name (unthemed_icon
->no_svg_filename
) != ICON_SUFFIX_PNG
))
784 icon_info
->filename
= g_strdup (unthemed_icon
->svg_filename
);
785 else if (unthemed_icon
->no_svg_filename
)
786 icon_info
->filename
= g_strdup (unthemed_icon
->no_svg_filename
);
788 icon_info
->dir_type
= ICON_THEME_DIR_UNTHEMED
;
793 icon_info
->desired_size
= size
;
800 rox_icon_theme_error_quark (void)
804 q
= g_quark_from_static_string ("gtk-icon-theme-error-quark");
810 rox_icon_theme_load_icon (RoxIconTheme
*icon_theme
,
811 const gchar
*icon_name
,
813 RoxIconLookupFlags flags
,
816 RoxIconInfo
*icon_info
;
817 GdkPixbuf
*pixbuf
= NULL
;
819 g_return_val_if_fail (ROX_IS_ICON_THEME (icon_theme
), NULL
);
820 g_return_val_if_fail (icon_name
!= NULL
, NULL
);
821 g_return_val_if_fail ((flags
& ROX_ICON_LOOKUP_NO_SVG
) == 0 ||
822 (flags
& ROX_ICON_LOOKUP_FORCE_SVG
) == 0, NULL
);
823 g_return_val_if_fail (error
== NULL
|| *error
== NULL
, NULL
);
825 icon_info
= rox_icon_theme_lookup_icon (icon_theme
, icon_name
, size
,
826 flags
| ROX_ICON_LOOKUP_USE_BUILTIN
);
829 g_set_error (error
, ROX_ICON_THEME_ERROR
, ROX_ICON_THEME_NOT_FOUND
,
830 _("Icon '%s' not present in theme"), icon_name
);
834 pixbuf
= rox_icon_info_load_icon (icon_info
, error
);
835 rox_icon_info_free (icon_info
);
841 rox_icon_theme_rescan_if_needed (RoxIconTheme
*icon_theme
)
843 RoxIconThemePrivate
*priv
;
844 IconThemeDirMtime
*dir_mtime
;
847 struct stat stat_buf
;
850 g_return_val_if_fail (ROX_IS_ICON_THEME (icon_theme
), FALSE
);
852 priv
= icon_theme
->priv
;
854 for (d
= priv
->dir_mtimes
; d
!= NULL
; d
= d
->next
)
858 stat_res
= stat (dir_mtime
->dir
, &stat_buf
);
860 /* dir mtime didn't change */
862 S_ISDIR (stat_buf
.st_mode
) &&
863 dir_mtime
->mtime
== stat_buf
.st_mtime
)
865 /* didn't exist before, and still doesn't */
866 if (dir_mtime
->mtime
== 0 &&
867 (stat_res
!= 0 || !S_ISDIR (stat_buf
.st_mode
)))
870 do_theme_change (icon_theme
);
874 g_get_current_time (&tv
);
875 priv
->last_stat_time
= tv
.tv_sec
;
881 theme_destroy (IconTheme
*theme
)
883 g_free (theme
->display_name
);
884 g_free (theme
->comment
);
885 g_free (theme
->name
);
886 g_free (theme
->example
);
888 g_list_foreach (theme
->dirs
, (GFunc
)theme_dir_destroy
, NULL
);
889 g_list_free (theme
->dirs
);
894 theme_dir_destroy (IconThemeDir
*dir
)
896 g_hash_table_destroy (dir
->icons
);
898 g_hash_table_destroy (dir
->icon_data
);
904 theme_dir_size_difference (IconThemeDir
*dir
, int size
, gboolean
*smaller
)
909 case ICON_THEME_DIR_FIXED
:
910 *smaller
= size
< dir
->size
;
911 return abs (size
- dir
->size
);
913 case ICON_THEME_DIR_SCALABLE
:
914 *smaller
= size
< dir
->min_size
;
915 if (size
< dir
->min_size
)
916 return dir
->min_size
- size
;
917 if (size
> dir
->max_size
)
918 return size
- dir
->max_size
;
921 case ICON_THEME_DIR_THRESHOLD
:
922 min
= dir
->size
- dir
->threshold
;
923 max
= dir
->size
+ dir
->threshold
;
924 *smaller
= size
< min
;
931 case ICON_THEME_DIR_UNTHEMED
:
932 g_assert_not_reached ();
935 g_assert_not_reached ();
940 string_from_suffix (IconSuffix suffix
)
944 case ICON_SUFFIX_XPM
:
946 case ICON_SUFFIX_SVG
:
948 case ICON_SUFFIX_PNG
:
951 g_assert_not_reached();
957 suffix_from_name (const char *name
)
961 if (g_str_has_suffix (name
, ".png"))
962 retval
= ICON_SUFFIX_PNG
;
963 else if (g_str_has_suffix (name
, ".svg"))
964 retval
= ICON_SUFFIX_SVG
;
965 else if (g_str_has_suffix (name
, ".xpm"))
966 retval
= ICON_SUFFIX_XPM
;
968 retval
= ICON_SUFFIX_NONE
;
974 best_suffix (IconSuffix suffix
,
977 if ((suffix
& ICON_SUFFIX_PNG
) != 0)
978 return ICON_SUFFIX_PNG
;
979 else if (allow_svg
&& ((suffix
& ICON_SUFFIX_SVG
) != 0))
980 return ICON_SUFFIX_SVG
;
981 else if ((suffix
& ICON_SUFFIX_XPM
) != 0)
982 return ICON_SUFFIX_XPM
;
984 return ICON_SUFFIX_NONE
;
988 theme_lookup_icon (IconTheme
*theme
,
989 const char *icon_name
,
992 gboolean use_builtin
)
995 IconThemeDir
*dir
, *min_dir
;
997 int min_difference
, difference
;
998 BuiltinIcon
*closest_builtin
= NULL
;
999 gboolean smaller
, has_larger
;
1002 min_difference
= G_MAXINT
;
1006 /* Builtin icons are logically part of the default theme and
1007 * are searched before other subdirectories of the default theme.
1009 if (strcmp (theme
->name
, DEFAULT_THEME_NAME
) == 0 && use_builtin
)
1011 closest_builtin
= find_builtin_icon (icon_name
, size
,
1015 if (min_difference
== 0)
1016 return icon_info_new_builtin (closest_builtin
);
1024 suffix
= GPOINTER_TO_UINT (g_hash_table_lookup (dir
->icons
, icon_name
));
1026 if (suffix
!= ICON_SUFFIX_NONE
&&
1027 (allow_svg
|| suffix
!= ICON_SUFFIX_SVG
))
1029 difference
= theme_dir_size_difference (dir
, size
, &smaller
);
1031 if (difference
== 0)
1039 if (difference
< min_difference
|| smaller
)
1041 min_difference
= difference
;
1043 closest_builtin
= NULL
;
1044 has_larger
= smaller
;
1049 if (difference
< min_difference
&& smaller
)
1051 min_difference
= difference
;
1053 closest_builtin
= NULL
;
1062 if (closest_builtin
)
1063 return icon_info_new_builtin (closest_builtin
);
1067 RoxIconInfo
*icon_info
= icon_info_new ();
1069 suffix
= GPOINTER_TO_UINT (g_hash_table_lookup (min_dir
->icons
, icon_name
));
1070 suffix
= best_suffix (suffix
, allow_svg
);
1071 g_assert (suffix
!= ICON_SUFFIX_NONE
);
1073 file
= g_strconcat (icon_name
, string_from_suffix (suffix
), NULL
);
1074 icon_info
->filename
= g_build_filename (min_dir
->dir
, file
, NULL
);
1077 if (min_dir
->icon_data
!= NULL
)
1078 icon_info
->data
= g_hash_table_lookup (min_dir
->icon_data
, icon_name
);
1080 icon_info
->dir_type
= min_dir
->type
;
1081 icon_info
->dir_size
= min_dir
->size
;
1082 icon_info
->threshold
= min_dir
->threshold
;
1091 load_icon_data (IconThemeDir
*dir
, const char *path
, const char *name
)
1093 GtkIconThemeFile
*icon_file
;
1104 if (g_file_get_contents (path
, &contents
, NULL
, NULL
))
1106 icon_file
= _rox_icon_theme_file_new_from_string (contents
, NULL
);
1110 base_name
= g_strdup (name
);
1111 dot
= strrchr (base_name
, '.');
1114 data
= g_new0 (GtkIconData
, 1);
1115 g_hash_table_replace (dir
->icon_data
, base_name
, data
);
1117 if (_rox_icon_theme_file_get_string (icon_file
, "Icon Data",
1118 "EmbeddedTextRectangle",
1121 split
= g_strsplit (str
, ",", 4);
1124 while (split
[i
] != NULL
)
1129 data
->has_embedded_rect
= TRUE
;
1130 data
->x0
= atoi (split
[0]);
1131 data
->y0
= atoi (split
[1]);
1132 data
->x1
= atoi (split
[2]);
1133 data
->y1
= atoi (split
[3]);
1141 if (_rox_icon_theme_file_get_string (icon_file
, "Icon Data",
1145 split
= g_strsplit (str
, "|", -1);
1148 while (split
[i
] != NULL
)
1151 data
->n_attach_points
= i
;
1152 data
->attach_points
= g_malloc (sizeof (GdkPoint
) * i
);
1155 while (split
[i
] != NULL
&& i
< data
->n_attach_points
)
1157 split_point
= strchr (split
[i
], ',');
1162 data
->attach_points
[i
].x
= atoi (split
[i
]);
1163 data
->attach_points
[i
].y
= atoi (split_point
);
1172 _rox_icon_theme_file_get_locale_string (icon_file
, "Icon Data",
1174 &data
->display_name
);
1176 _rox_icon_theme_file_free (icon_file
);
1184 scan_directory (RoxIconThemePrivate
*icon_theme
,
1185 IconThemeDir
*dir
, char *full_dir
)
1189 char *base_name
, *dot
;
1191 IconSuffix suffix
, hash_suffix
;
1193 dir
->icons
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
1196 gdir
= g_dir_open (full_dir
, 0, NULL
);
1201 while ((name
= g_dir_read_name (gdir
)))
1203 if (g_str_has_suffix (name
, ".icon"))
1205 if (dir
->icon_data
== NULL
)
1206 dir
->icon_data
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
1207 g_free
, (GDestroyNotify
)icon_data_free
);
1209 path
= g_build_filename (full_dir
, name
, NULL
);
1210 if (g_file_test (path
, G_FILE_TEST_IS_REGULAR
))
1211 load_icon_data (dir
, path
, name
);
1218 suffix
= suffix_from_name (name
);
1219 if (suffix
== ICON_SUFFIX_NONE
)
1222 base_name
= g_strdup (name
);
1223 dot
= strrchr (base_name
, '.');
1226 hash_suffix
= GPOINTER_TO_INT (g_hash_table_lookup (dir
->icons
, base_name
));
1227 g_hash_table_replace (dir
->icons
, base_name
, GUINT_TO_POINTER (hash_suffix
| suffix
));
1228 g_hash_table_insert (icon_theme
->all_icons
, base_name
, NULL
);
1235 theme_subdir_load (RoxIconTheme
*icon_theme
,
1237 GtkIconThemeFile
*theme_file
,
1243 IconThemeDirType type
;
1244 char *context_string
;
1252 if (!_rox_icon_theme_file_get_integer (theme_file
,
1257 g_warning ("Theme directory %s of theme %s has no size field\n", subdir
, theme
->name
);
1261 type
= ICON_THEME_DIR_THRESHOLD
;
1262 if (_rox_icon_theme_file_get_string (theme_file
, subdir
, "Type", &type_string
))
1264 if (strcmp (type_string
, "Fixed") == 0)
1265 type
= ICON_THEME_DIR_FIXED
;
1266 else if (strcmp (type_string
, "Scalable") == 0)
1267 type
= ICON_THEME_DIR_SCALABLE
;
1268 else if (strcmp (type_string
, "Threshold") == 0)
1269 type
= ICON_THEME_DIR_THRESHOLD
;
1271 g_free (type_string
);
1275 if (_rox_icon_theme_file_get_string (theme_file
, subdir
, "Context", &context_string
))
1277 context
= g_quark_from_string (context_string
);
1278 g_free (context_string
);
1281 if (!_rox_icon_theme_file_get_integer (theme_file
,
1287 if (!_rox_icon_theme_file_get_integer (theme_file
,
1293 if (!_rox_icon_theme_file_get_integer (theme_file
,
1299 for (base
= 0; base
< icon_theme
->priv
->search_path_len
; base
++)
1301 full_dir
= g_build_filename (icon_theme
->priv
->search_path
[base
],
1305 if (g_file_test (full_dir
, G_FILE_TEST_IS_DIR
))
1307 dir
= g_new (IconThemeDir
, 1);
1309 dir
->context
= context
;
1311 dir
->min_size
= min_size
;
1312 dir
->max_size
= max_size
;
1313 dir
->threshold
= threshold
;
1314 dir
->dir
= full_dir
;
1315 dir
->icon_data
= NULL
;
1317 scan_directory (icon_theme
->priv
, dir
, full_dir
);
1319 theme
->dirs
= g_list_append (theme
->dirs
, dir
);
1327 icon_data_free (GtkIconData
*icon_data
)
1329 g_free (icon_data
->attach_points
);
1330 g_free (icon_data
->display_name
);
1338 rox_icon_info_get_type (void)
1340 static GType our_type
= 0;
1343 our_type
= g_boxed_type_register_static ("RoxIconInfo",
1344 (GBoxedCopyFunc
) rox_icon_info_copy
,
1345 (GBoxedFreeFunc
) rox_icon_info_free
);
1350 static RoxIconInfo
*
1351 icon_info_new (void)
1353 RoxIconInfo
*icon_info
= g_new0 (RoxIconInfo
, 1);
1355 icon_info
->ref_count
= 1;
1356 icon_info
->scale
= -1.;
1361 static RoxIconInfo
*
1362 icon_info_new_builtin (BuiltinIcon
*icon
)
1364 RoxIconInfo
*icon_info
= icon_info_new ();
1366 icon_info
->builtin_pixbuf
= g_object_ref (icon
->pixbuf
);
1367 icon_info
->dir_type
= ICON_THEME_DIR_THRESHOLD
;
1368 icon_info
->dir_size
= icon
->size
;
1369 icon_info
->threshold
= 2;
1375 rox_icon_info_copy (RoxIconInfo
*icon_info
)
1379 g_return_val_if_fail (icon_info
!= NULL
, NULL
);
1381 copy
= g_memdup (icon_info
, sizeof (RoxIconInfo
));
1382 if (copy
->builtin_pixbuf
)
1383 g_object_ref (copy
->builtin_pixbuf
);
1385 g_object_ref (copy
->pixbuf
);
1386 if (copy
->load_error
)
1387 copy
->load_error
= g_error_copy (copy
->load_error
);
1389 copy
->filename
= g_strdup (copy
->filename
);
1395 rox_icon_info_free (RoxIconInfo
*icon_info
)
1397 g_return_if_fail (icon_info
!= NULL
);
1399 if (icon_info
->filename
)
1400 g_free (icon_info
->filename
);
1401 if (icon_info
->builtin_pixbuf
)
1402 g_object_unref (icon_info
->builtin_pixbuf
);
1403 if (icon_info
->pixbuf
)
1404 g_object_unref (icon_info
->pixbuf
);
1409 /* This function contains the complicatd logic for deciding
1410 * on the size at which to load the icon and loading it at
1414 icon_info_ensure_scale_and_pixbuf (RoxIconInfo
*icon_info
,
1415 gboolean scale_only
)
1417 int image_width
, image_height
;
1418 GdkPixbuf
*source_pixbuf
;
1420 /* First check if we already succeeded have the necessary
1421 * information (or failed earlier)
1423 if (scale_only
&& icon_info
->scale
>= 0)
1426 if (icon_info
->pixbuf
)
1429 if (icon_info
->load_error
)
1432 /* SVG icons are a special case - we just immediately scale them
1433 * to the desired size
1435 if (icon_info
->filename
&& g_str_has_suffix (icon_info
->filename
, ".svg"))
1437 icon_info
->scale
= icon_info
->desired_size
/ 1000.;
1442 icon_info
->pixbuf
= rox_pixbuf_new_from_file_at_scale(icon_info
->filename
,
1443 icon_info
->desired_size
,
1444 icon_info
->desired_size
,
1446 &icon_info
->load_error
);
1448 return icon_info
->pixbuf
!= NULL
;
1451 /* In many cases, the scale can be determined without actual access
1452 * to the icon file. This is generally true when we have a size
1453 * for the directory where the icon is; the image size doesn't
1454 * matter in that case.
1456 if (icon_info
->dir_type
== ICON_THEME_DIR_FIXED
)
1457 icon_info
->scale
= 1.0;
1458 else if (icon_info
->dir_type
== ICON_THEME_DIR_THRESHOLD
)
1460 if (icon_info
->desired_size
>= icon_info
->dir_size
- icon_info
->threshold
&&
1461 icon_info
->desired_size
<= icon_info
->dir_size
+ icon_info
->threshold
)
1462 icon_info
->scale
= 1.0;
1463 else if (icon_info
->dir_size
> 0)
1464 icon_info
->scale
=(gdouble
) icon_info
->desired_size
/ icon_info
->dir_size
;
1466 else if (icon_info
->dir_type
== ICON_THEME_DIR_SCALABLE
)
1468 if (icon_info
->dir_size
> 0)
1469 icon_info
->scale
= (gdouble
) icon_info
->desired_size
/ icon_info
->dir_size
;
1472 if (icon_info
->scale
>= 0. && scale_only
)
1475 /* At this point, we need to actually get the icon; either from the
1476 * builting image or by loading the file
1478 if (icon_info
->builtin_pixbuf
)
1479 source_pixbuf
= g_object_ref (icon_info
->builtin_pixbuf
);
1482 source_pixbuf
= gdk_pixbuf_new_from_file (icon_info
->filename
,
1483 &icon_info
->load_error
);
1488 /* Do scale calculations that depend on the image size
1490 image_width
= gdk_pixbuf_get_width (source_pixbuf
);
1491 image_height
= gdk_pixbuf_get_height (source_pixbuf
);
1493 if (icon_info
->scale
< 0.0)
1495 gint image_size
= MAX (image_width
, image_height
);
1497 icon_info
->scale
= icon_info
->desired_size
/ image_size
;
1499 icon_info
->scale
= 1.0;
1501 if (icon_info
->dir_type
== ICON_THEME_DIR_UNTHEMED
)
1502 icon_info
->scale
= MAX (icon_info
->scale
, 1.0);
1505 /* We don't short-circuit out here for scale_only, since, now
1506 * we've loaded the icon, we might as well go ahead and finish
1507 * the job. This is a bit of a waste when we scale here
1508 * and never get the final pixbuf; at the cost of a bit of
1509 * extra complexity, we could keep the source pixbuf around
1510 * but not actually scale it until neede.
1513 if (icon_info
->scale
== 1.0)
1514 icon_info
->pixbuf
= source_pixbuf
;
1517 icon_info
->pixbuf
= gdk_pixbuf_scale_simple (source_pixbuf
,
1518 0.5 + image_width
* icon_info
->scale
,
1519 0.5 + image_height
* icon_info
->scale
,
1520 GDK_INTERP_BILINEAR
);
1521 g_object_unref (source_pixbuf
);
1528 rox_icon_info_load_icon (RoxIconInfo
*icon_info
,
1531 g_return_val_if_fail (icon_info
!= NULL
, NULL
);
1533 g_return_val_if_fail (icon_info
!= NULL
, NULL
);
1534 g_return_val_if_fail (error
== NULL
|| *error
== NULL
, NULL
);
1536 icon_info_ensure_scale_and_pixbuf (icon_info
, FALSE
);
1538 if (icon_info
->load_error
)
1540 g_propagate_error (error
, icon_info
->load_error
);
1544 return g_object_ref (icon_info
->pixbuf
);
1552 static BuiltinIcon
*
1553 find_builtin_icon (const gchar
*icon_name
,
1555 gint
*min_difference_p
,
1556 gboolean
*has_larger_p
)
1558 GSList
*icons
= NULL
;
1559 gint min_difference
= G_MAXINT
;
1560 gboolean has_larger
= FALSE
;
1561 BuiltinIcon
*min_icon
= NULL
;
1563 if (!icon_theme_builtin_icons
)
1566 icons
= g_hash_table_lookup (icon_theme_builtin_icons
, icon_name
);
1570 BuiltinIcon
*default_icon
= icons
->data
;
1571 int min
, max
, difference
;
1574 min
= default_icon
->size
- 2;
1575 max
= default_icon
->size
+ 2;
1576 smaller
= size
< min
;
1578 difference
= min
- size
;
1580 difference
= size
- max
;
1584 if (difference
== 0)
1586 min_icon
= default_icon
;
1592 if (difference
< min_difference
|| smaller
)
1594 min_difference
= difference
;
1595 min_icon
= default_icon
;
1596 has_larger
= smaller
;
1601 if (difference
< min_difference
&& smaller
)
1603 min_difference
= difference
;
1604 min_icon
= default_icon
;
1608 icons
= icons
->next
;
1611 if (min_difference_p
)
1612 *min_difference_p
= min_difference
;
1614 *has_larger_p
= has_larger
;