r3813: Defer re-generating a thumbnail until 5 seconds after the timestamp on the...
[rox-filer.git] / ROX-Filer / src / gtkicontheme.c
blob07e3b24f875514bdb6b0b1fd12ee74b3a4736241
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)
10 * any later version.
13 #include "config.h"
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #include <string.h>
21 #include <stdlib.h>
22 #include <glib.h>
24 #ifdef G_OS_WIN32
25 #ifndef S_ISDIR
26 #define S_ISDIR(mode) ((mode)&_S_IFDIR)
27 #endif
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,
40 GError **error);
42 typedef struct _GtkIconData GtkIconData;
44 typedef enum
46 ICON_THEME_DIR_FIXED,
47 ICON_THEME_DIR_SCALABLE,
48 ICON_THEME_DIR_THRESHOLD,
49 ICON_THEME_DIR_UNTHEMED
50 } IconThemeDirType;
52 /* In reverse search order: */
53 typedef enum
55 ICON_SUFFIX_NONE = 0,
56 ICON_SUFFIX_XPM = 1 << 0,
57 ICON_SUFFIX_SVG = 1 << 1,
58 ICON_SUFFIX_PNG = 1 << 2,
59 } IconSuffix;
61 struct _RoxIconThemePrivate
63 guint custom_theme : 1;
64 guint pixbuf_supports_svg : 1;
66 char *current_theme;
67 char **search_path;
68 int search_path_len;
70 gboolean themes_valid;
71 /* A list of all the themes needed to look up icons.
72 * In search order, without duplicates
74 GList *themes;
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 */
83 long last_stat_time;
84 GList *dir_mtimes;
87 struct _RoxIconInfo
89 guint ref_count;
91 /* Information about the source
93 gchar *filename;
94 GdkPixbuf *builtin_pixbuf;
96 GtkIconData *data;
98 /* Information about the directory where
99 * the source was found
101 IconThemeDirType dir_type;
102 gint dir_size;
103 gint threshold;
105 /* Parameters influencing the scaled icon
107 gint desired_size;
108 gboolean raw_coordinates;
110 /* Cached information if we go ahead and try to load
111 * the icon.
113 GdkPixbuf *pixbuf;
114 GError *load_error;
115 gdouble scale;
118 typedef struct
120 char *name;
121 char *display_name;
122 char *comment;
123 char *example;
125 /* In search order */
126 GList *dirs;
127 } IconTheme;
129 struct _GtkIconData
131 gboolean has_embedded_rect;
132 gint x0, y0, x1, y1;
134 GdkPoint *attach_points;
135 gint n_attach_points;
137 gchar *display_name;
140 typedef struct
142 IconThemeDirType type;
143 GQuark context;
145 int size;
146 int min_size;
147 int max_size;
148 int threshold;
150 char *dir;
152 GHashTable *icons;
153 GHashTable *icon_data;
154 } IconThemeDir;
156 typedef struct
158 char *svg_filename;
159 char *no_svg_filename;
160 } UnthemedIcon;
162 typedef struct
164 gint size;
165 GdkPixbuf *pixbuf;
166 } BuiltinIcon;
168 typedef struct
170 char *dir;
171 time_t mtime; /* 0 == not existing or not a dir */
172 } IconThemeDirMtime;
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,
182 int size,
183 gboolean allow_svg,
184 gboolean use_default_icons);
185 static void theme_subdir_load (RoxIconTheme *icon_theme,
186 IconTheme *theme,
187 GtkIconThemeFile *theme_file,
188 char *subdir);
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,
201 gint size,
202 gint *min_difference_p,
203 gboolean *has_larger_p);
205 static guint signal_changed = 0;
207 static GHashTable *icon_theme_builtin_icons;
209 GType
210 rox_icon_theme_get_type (void)
212 static GType type = 0;
214 if (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),
225 0, /* n_preallocs */
226 (GInstanceInitFunc) rox_icon_theme_init,
229 type = g_type_register_static (G_TYPE_OBJECT, "RoxIconTheme", &info, 0);
232 return type;
235 RoxIconTheme *
236 rox_icon_theme_new (void)
238 return g_object_new (ROX_TYPE_ICON_THEME, NULL);
241 static void
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
254 * icon theme.
256 signal_changed = g_signal_new ("changed",
257 G_TYPE_FROM_CLASS (klass),
258 G_SIGNAL_RUN_LAST,
259 G_STRUCT_OFFSET (RoxIconThemeClass, changed),
260 NULL, NULL,
261 g_cclosure_marshal_VOID__VOID,
262 G_TYPE_NONE, 0);
264 /* g_type_class_add_private (klass, sizeof (RoxIconThemePrivate)); */
267 static void
268 update_current_theme (RoxIconTheme *icon_theme)
270 RoxIconThemePrivate *priv = icon_theme->priv;
272 if (!priv->custom_theme)
274 gchar *theme = NULL;
276 if (!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);
286 else
287 g_free (theme);
291 static gboolean
292 pixbuf_supports_svg ()
294 GSList *formats = gdk_pixbuf_get_formats ();
295 GSList *tmp_list;
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);
301 gchar **mime_type;
303 for (mime_type = mime_types; *mime_type && !found_svg; mime_type++)
305 if (strcmp (*mime_type, "image/svg") == 0)
306 found_svg = TRUE;
309 g_strfreev (mime_types);
312 g_slist_free (formats);
314 return found_svg;
317 static void
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;
338 priv->themes = NULL;
339 priv->unthemed_icons = NULL;
341 priv->pixbuf_supports_svg = pixbuf_supports_svg ();
344 static void
345 free_dir_mtime (IconThemeDirMtime *dir_mtime)
347 g_free (dir_mtime->dir);
348 g_free (dir_mtime);
351 static void
352 do_theme_change (RoxIconTheme *icon_theme)
354 blow_themes (icon_theme);
355 g_signal_emit (G_OBJECT (icon_theme), signal_changed, 0);
358 static void
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);
372 priv->themes = NULL;
373 priv->unthemed_icons = NULL;
374 priv->dir_mtimes = NULL;
375 priv->all_icons = NULL;
376 priv->themes_valid = FALSE;
379 static void
380 rox_icon_theme_finalize (GObject *object)
382 RoxIconTheme *icon_theme;
383 RoxIconThemePrivate *priv;
384 int i;
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);
401 void
402 rox_icon_theme_get_search_path (RoxIconTheme *icon_theme,
403 gchar **path[],
404 gint *n_elements)
406 RoxIconThemePrivate *priv;
407 int i;
409 g_return_if_fail (ROX_IS_ICON_THEME (icon_theme));
411 priv = icon_theme->priv;
413 if (n_elements)
414 *n_elements = priv->search_path_len;
416 if (path)
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) */
421 (*path)[i] = NULL;
425 void
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);
446 else
448 priv->custom_theme = FALSE;
450 update_current_theme (icon_theme);
454 static void
455 insert_theme (RoxIconTheme *icon_theme, const char *theme_name)
457 int i;
458 GList *l;
459 char **dirs;
460 char **themes;
461 RoxIconThemePrivate *priv;
462 IconTheme *theme;
463 char *path;
464 char *contents;
465 char *directories;
466 char *inherits;
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)
475 theme = l->data;
476 if (strcmp (theme->name, theme_name) == 0)
477 return;
480 for (i = 0; i < priv->search_path_len; i++)
482 path = g_build_filename (priv->search_path[i],
483 theme_name,
484 NULL);
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;
489 else
490 dir_mtime->mtime = 0;
492 priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime);
495 theme_file = NULL;
496 for (i = 0; i < priv->search_path_len; i++)
498 path = g_build_filename (priv->search_path[i],
499 theme_name,
500 "index.theme",
501 NULL);
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);
505 g_free (contents);
506 g_free (path);
507 break;
510 g_free (path);
513 if (theme_file == NULL)
514 return;
516 theme = g_new (IconTheme, 1);
517 if (!_rox_icon_theme_file_get_locale_string (theme_file,
518 "Icon Theme",
519 "Name",
520 &theme->display_name))
522 g_warning ("Theme file for %s has no name\n", theme_name);
523 g_free (theme);
524 _rox_icon_theme_file_free (theme_file);
525 return;
528 if (!_rox_icon_theme_file_get_string (theme_file,
529 "Icon Theme",
530 "Directories",
531 &directories))
533 g_warning ("Theme file for %s has no directories\n", theme_name);
534 g_free (theme->display_name);
535 g_free (theme);
536 _rox_icon_theme_file_free (theme_file);
537 return;
540 theme->name = g_strdup (theme_name);
541 _rox_icon_theme_file_get_locale_string (theme_file,
542 "Icon Theme",
543 "Comment",
544 &theme->comment);
545 _rox_icon_theme_file_get_string (theme_file,
546 "Icon Theme",
547 "Example",
548 &theme->example);
550 dirs = g_strsplit (directories, ",", 0);
552 theme->dirs = NULL;
553 for (i = 0; dirs[i] != NULL; i++)
554 theme_subdir_load (icon_theme, theme, theme_file, dirs[i]);
556 g_strfreev (dirs);
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,
566 "Icon Theme",
567 "Inherits",
568 &inherits))
570 themes = g_strsplit (inherits, ",", 0);
572 for (i = 0; themes[i] != NULL; i++)
573 insert_theme (icon_theme, themes[i]);
575 g_strfreev (themes);
577 g_free (inherits);
580 _rox_icon_theme_file_free (theme_file);
583 static void
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);
593 static void
594 load_themes (RoxIconTheme *icon_theme)
596 RoxIconThemePrivate *priv;
597 GDir *gdir;
598 int base;
599 char *dir, *base_name, *dot;
600 const char *file;
601 char *abs_file;
602 UnthemedIcon *unthemed_icon;
603 IconSuffix old_suffix, new_suffix;
604 GTimeVal tv;
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);
624 if (gdir == NULL)
625 continue;
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, '.');
638 if (dot)
639 *dot = 0;
641 if ((unthemed_icon = g_hash_table_lookup (priv->unthemed_icons,
642 base_name)))
644 if (new_suffix == ICON_SUFFIX_SVG)
646 if (unthemed_icon->no_svg_filename)
647 g_free (abs_file);
648 else
649 unthemed_icon->svg_filename = abs_file;
651 else
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;
661 else
662 g_free (abs_file);
664 else
665 unthemed_icon->no_svg_filename = abs_file;
668 g_free (base_name);
670 else
672 unthemed_icon = g_new0 (UnthemedIcon, 1);
674 if (new_suffix == ICON_SUFFIX_SVG)
675 unthemed_icon->svg_filename = abs_file;
676 else
677 unthemed_icon->svg_filename = abs_file;
679 g_hash_table_insert (priv->unthemed_icons,
680 base_name,
681 unthemed_icon);
682 g_hash_table_insert (priv->all_icons,
683 base_name, NULL);
687 g_dir_close (gdir);
690 priv->themes_valid = TRUE;
692 g_get_current_time(&tv);
693 priv->last_stat_time = tv.tv_sec;
696 static void
697 ensure_valid_themes (RoxIconTheme *icon_theme)
699 RoxIconThemePrivate *priv = icon_theme->priv;
700 GTimeVal tv;
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);
714 RoxIconInfo *
715 rox_icon_theme_lookup_icon (RoxIconTheme *icon_theme,
716 const gchar *icon_name,
717 gint size,
718 RoxIconLookupFlags flags)
720 RoxIconThemePrivate *priv;
721 GList *l;
722 RoxIconInfo *icon_info = NULL;
723 UnthemedIcon *unthemed_icon;
724 gboolean allow_svg;
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)
736 allow_svg = FALSE;
737 else if (flags & ROX_ICON_LOOKUP_FORCE_SVG)
738 allow_svg = TRUE;
739 else
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;
747 l = priv->themes;
748 while (l != NULL)
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);
756 if (icon_info)
757 goto out;
759 l = l->next;
762 if (!found_default)
764 BuiltinIcon *builtin = find_builtin_icon (icon_name, size, NULL, NULL);
765 if (builtin)
767 icon_info = icon_info_new_builtin (builtin);
768 goto out;
772 unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, icon_name);
773 if (unthemed_icon)
775 icon_info = icon_info_new ();
777 /* A SVG icon, when allowed, beats out a XPM icon, but not
778 * a PNG icon
780 if (allow_svg &&
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;
791 out:
792 if (icon_info)
793 icon_info->desired_size = size;
795 return icon_info;
798 /* Error quark */
799 GQuark
800 rox_icon_theme_error_quark (void)
802 static GQuark q = 0;
803 if (q == 0)
804 q = g_quark_from_static_string ("gtk-icon-theme-error-quark");
806 return q;
809 GdkPixbuf *
810 rox_icon_theme_load_icon (RoxIconTheme *icon_theme,
811 const gchar *icon_name,
812 gint size,
813 RoxIconLookupFlags flags,
814 GError **error)
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);
827 if (!icon_info)
829 g_set_error (error, ROX_ICON_THEME_ERROR, ROX_ICON_THEME_NOT_FOUND,
830 _("Icon '%s' not present in theme"), icon_name);
831 return NULL;
834 pixbuf = rox_icon_info_load_icon (icon_info, error);
835 rox_icon_info_free (icon_info);
837 return pixbuf;
840 gboolean
841 rox_icon_theme_rescan_if_needed (RoxIconTheme *icon_theme)
843 RoxIconThemePrivate *priv;
844 IconThemeDirMtime *dir_mtime;
845 GList *d;
846 int stat_res;
847 struct stat stat_buf;
848 GTimeVal tv;
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)
856 dir_mtime = d->data;
858 stat_res = stat (dir_mtime->dir, &stat_buf);
860 /* dir mtime didn't change */
861 if (stat_res == 0 &&
862 S_ISDIR (stat_buf.st_mode) &&
863 dir_mtime->mtime == stat_buf.st_mtime)
864 continue;
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)))
868 continue;
870 do_theme_change (icon_theme);
871 return TRUE;
874 g_get_current_time (&tv);
875 priv->last_stat_time = tv.tv_sec;
877 return FALSE;
880 static void
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);
890 g_free (theme);
893 static void
894 theme_dir_destroy (IconThemeDir *dir)
896 g_hash_table_destroy (dir->icons);
897 if (dir->icon_data)
898 g_hash_table_destroy (dir->icon_data);
899 g_free (dir->dir);
900 g_free (dir);
903 static int
904 theme_dir_size_difference (IconThemeDir *dir, int size, gboolean *smaller)
906 int min, max;
907 switch (dir->type)
909 case ICON_THEME_DIR_FIXED:
910 *smaller = size < dir->size;
911 return abs (size - dir->size);
912 break;
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;
919 return 0;
920 break;
921 case ICON_THEME_DIR_THRESHOLD:
922 min = dir->size - dir->threshold;
923 max = dir->size + dir->threshold;
924 *smaller = size < min;
925 if (size < min)
926 return min - size;
927 if (size > max)
928 return size - max;
929 return 0;
930 break;
931 case ICON_THEME_DIR_UNTHEMED:
932 g_assert_not_reached ();
933 break;
935 g_assert_not_reached ();
936 return 1000;
939 static const char *
940 string_from_suffix (IconSuffix suffix)
942 switch (suffix)
944 case ICON_SUFFIX_XPM:
945 return ".xpm";
946 case ICON_SUFFIX_SVG:
947 return ".svg";
948 case ICON_SUFFIX_PNG:
949 return ".png";
950 default:
951 g_assert_not_reached();
953 return NULL;
956 static IconSuffix
957 suffix_from_name (const char *name)
959 IconSuffix retval;
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;
967 else
968 retval = ICON_SUFFIX_NONE;
970 return retval;
973 static IconSuffix
974 best_suffix (IconSuffix suffix,
975 gboolean allow_svg)
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;
983 else
984 return ICON_SUFFIX_NONE;
987 static RoxIconInfo *
988 theme_lookup_icon (IconTheme *theme,
989 const char *icon_name,
990 int size,
991 gboolean allow_svg,
992 gboolean use_builtin)
994 GList *l;
995 IconThemeDir *dir, *min_dir;
996 char *file;
997 int min_difference, difference;
998 BuiltinIcon *closest_builtin = NULL;
999 gboolean smaller, has_larger;
1000 IconSuffix suffix;
1002 min_difference = G_MAXINT;
1003 min_dir = NULL;
1004 has_larger = FALSE;
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,
1012 &min_difference,
1013 &has_larger);
1015 if (min_difference == 0)
1016 return icon_info_new_builtin (closest_builtin);
1019 l = theme->dirs;
1020 while (l != NULL)
1022 dir = l->data;
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)
1033 min_dir = dir;
1034 break;
1037 if (!has_larger)
1039 if (difference < min_difference || smaller)
1041 min_difference = difference;
1042 min_dir = dir;
1043 closest_builtin = NULL;
1044 has_larger = smaller;
1047 else
1049 if (difference < min_difference && smaller)
1051 min_difference = difference;
1052 min_dir = dir;
1053 closest_builtin = NULL;
1059 l = l->next;
1062 if (closest_builtin)
1063 return icon_info_new_builtin (closest_builtin);
1065 if (min_dir)
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);
1075 g_free (file);
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;
1084 return icon_info;
1087 return NULL;
1090 static void
1091 load_icon_data (IconThemeDir *dir, const char *path, const char *name)
1093 GtkIconThemeFile *icon_file;
1094 char *base_name;
1095 char **split;
1096 char *contents;
1097 char *dot;
1098 char *str;
1099 char *split_point;
1100 int i;
1102 GtkIconData *data;
1104 if (g_file_get_contents (path, &contents, NULL, NULL))
1106 icon_file = _rox_icon_theme_file_new_from_string (contents, NULL);
1108 if (icon_file)
1110 base_name = g_strdup (name);
1111 dot = strrchr (base_name, '.');
1112 *dot = 0;
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",
1119 &str))
1121 split = g_strsplit (str, ",", 4);
1123 i = 0;
1124 while (split[i] != NULL)
1125 i++;
1127 if (i==4)
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]);
1136 g_strfreev (split);
1137 g_free (str);
1141 if (_rox_icon_theme_file_get_string (icon_file, "Icon Data",
1142 "AttachPoints",
1143 &str))
1145 split = g_strsplit (str, "|", -1);
1147 i = 0;
1148 while (split[i] != NULL)
1149 i++;
1151 data->n_attach_points = i;
1152 data->attach_points = g_malloc (sizeof (GdkPoint) * i);
1154 i = 0;
1155 while (split[i] != NULL && i < data->n_attach_points)
1157 split_point = strchr (split[i], ',');
1158 if (split_point)
1160 *split_point = 0;
1161 split_point++;
1162 data->attach_points[i].x = atoi (split[i]);
1163 data->attach_points[i].y = atoi (split_point);
1165 i++;
1168 g_strfreev (split);
1169 g_free (str);
1172 _rox_icon_theme_file_get_locale_string (icon_file, "Icon Data",
1173 "DisplayName",
1174 &data->display_name);
1176 _rox_icon_theme_file_free (icon_file);
1178 g_free (contents);
1183 static void
1184 scan_directory (RoxIconThemePrivate *icon_theme,
1185 IconThemeDir *dir, char *full_dir)
1187 GDir *gdir;
1188 const char *name;
1189 char *base_name, *dot;
1190 char *path;
1191 IconSuffix suffix, hash_suffix;
1193 dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal,
1194 g_free, NULL);
1196 gdir = g_dir_open (full_dir, 0, NULL);
1198 if (gdir == NULL)
1199 return;
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);
1213 g_free (path);
1215 continue;
1218 suffix = suffix_from_name (name);
1219 if (suffix == ICON_SUFFIX_NONE)
1220 continue;
1222 base_name = g_strdup (name);
1223 dot = strrchr (base_name, '.');
1224 *dot = 0;
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);
1231 g_dir_close (gdir);
1234 static void
1235 theme_subdir_load (RoxIconTheme *icon_theme,
1236 IconTheme *theme,
1237 GtkIconThemeFile *theme_file,
1238 char *subdir)
1240 int base;
1241 char *type_string;
1242 IconThemeDir *dir;
1243 IconThemeDirType type;
1244 char *context_string;
1245 GQuark context;
1246 int size;
1247 int min_size;
1248 int max_size;
1249 int threshold;
1250 char *full_dir;
1252 if (!_rox_icon_theme_file_get_integer (theme_file,
1253 subdir,
1254 "Size",
1255 &size))
1257 g_warning ("Theme directory %s of theme %s has no size field\n", subdir, theme->name);
1258 return;
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);
1274 context = 0;
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,
1282 subdir,
1283 "MaxSize",
1284 &max_size))
1285 max_size = size;
1287 if (!_rox_icon_theme_file_get_integer (theme_file,
1288 subdir,
1289 "MinSize",
1290 &min_size))
1291 min_size = size;
1293 if (!_rox_icon_theme_file_get_integer (theme_file,
1294 subdir,
1295 "Threshold",
1296 &threshold))
1297 threshold = 2;
1299 for (base = 0; base < icon_theme->priv->search_path_len; base++)
1301 full_dir = g_build_filename (icon_theme->priv->search_path[base],
1302 theme->name,
1303 subdir,
1304 NULL);
1305 if (g_file_test (full_dir, G_FILE_TEST_IS_DIR))
1307 dir = g_new (IconThemeDir, 1);
1308 dir->type = type;
1309 dir->context = context;
1310 dir->size = size;
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);
1321 else
1322 g_free (full_dir);
1326 static void
1327 icon_data_free (GtkIconData *icon_data)
1329 g_free (icon_data->attach_points);
1330 g_free (icon_data->display_name);
1331 g_free (icon_data);
1335 * RoxIconInfo
1337 GType
1338 rox_icon_info_get_type (void)
1340 static GType our_type = 0;
1342 if (our_type == 0)
1343 our_type = g_boxed_type_register_static ("RoxIconInfo",
1344 (GBoxedCopyFunc) rox_icon_info_copy,
1345 (GBoxedFreeFunc) rox_icon_info_free);
1347 return our_type;
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.;
1358 return icon_info;
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;
1371 return icon_info;
1374 RoxIconInfo *
1375 rox_icon_info_copy (RoxIconInfo *icon_info)
1377 RoxIconInfo *copy;
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);
1384 if (copy->pixbuf)
1385 g_object_ref (copy->pixbuf);
1386 if (copy->load_error)
1387 copy->load_error = g_error_copy (copy->load_error);
1388 if (copy->filename)
1389 copy->filename = g_strdup (copy->filename);
1391 return copy;
1394 void
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);
1406 g_free (icon_info);
1409 /* This function contains the complicatd logic for deciding
1410 * on the size at which to load the icon and loading it at
1411 * that size.
1413 static gboolean
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)
1424 return TRUE;
1426 if (icon_info->pixbuf)
1427 return TRUE;
1429 if (icon_info->load_error)
1430 return FALSE;
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.;
1439 if (scale_only)
1440 return TRUE;
1442 icon_info->pixbuf = rox_pixbuf_new_from_file_at_scale(icon_info->filename,
1443 icon_info->desired_size,
1444 icon_info->desired_size,
1445 TRUE,
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)
1473 return TRUE;
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);
1480 else
1482 source_pixbuf = gdk_pixbuf_new_from_file (icon_info->filename,
1483 &icon_info->load_error);
1484 if (!source_pixbuf)
1485 return FALSE;
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);
1496 if (image_size > 0)
1497 icon_info->scale = icon_info->desired_size / image_size;
1498 else
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;
1515 else
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);
1524 return TRUE;
1527 static GdkPixbuf *
1528 rox_icon_info_load_icon (RoxIconInfo *icon_info,
1529 GError **error)
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);
1541 return NULL;
1544 return g_object_ref (icon_info->pixbuf);
1548 * Builtin icons
1552 static BuiltinIcon *
1553 find_builtin_icon (const gchar *icon_name,
1554 gint size,
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)
1564 return NULL;
1566 icons = g_hash_table_lookup (icon_theme_builtin_icons, icon_name);
1568 while (icons)
1570 BuiltinIcon *default_icon = icons->data;
1571 int min, max, difference;
1572 gboolean smaller;
1574 min = default_icon->size - 2;
1575 max = default_icon->size + 2;
1576 smaller = size < min;
1577 if (size < min)
1578 difference = min - size;
1579 if (size > max)
1580 difference = size - max;
1581 else
1582 difference = 0;
1584 if (difference == 0)
1586 min_icon = default_icon;
1587 break;
1590 if (!has_larger)
1592 if (difference < min_difference || smaller)
1594 min_difference = difference;
1595 min_icon = default_icon;
1596 has_larger = smaller;
1599 else
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;
1613 if (has_larger_p)
1614 *has_larger_p = has_larger;
1616 return min_icon;