gtkicontheme.c 169 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 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
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17
 */

18 19
#include "config.h"

20 21
#include <sys/types.h>
#include <sys/stat.h>
22
#ifdef HAVE_UNISTD_H
23
#include <unistd.h>
24
#endif
25 26
#include <string.h>
#include <stdlib.h>
27
#include <math.h>
28
#include <glib.h>
29
#include <glib/gstdio.h>
30

31 32 33 34
#ifdef G_OS_WIN32
#ifndef S_ISDIR
#define S_ISDIR(mode) ((mode)&_S_IFDIR)
#endif
35
#define WIN32_LEAN_AND_MEAN
36
#include <windows.h>
37
#include <shellapi.h>
38
#include "win32/gdkwin32.h"
39 40
#endif /* G_OS_WIN32 */

41
#include "gtkiconthemeprivate.h"
42 43
#include "gtkcsspalettevalueprivate.h"
#include "gtkcssrgbavalueprivate.h"
44
#include "gtkdebug.h"
45
#include "deprecated/gtkiconfactory.h"
46
#include "gtkiconcache.h"
47
#include "gtkintl.h"
48
#include "gtkmain.h"
49
#include "deprecated/gtknumerableiconprivate.h"
50
#include "gtksettingsprivate.h"
51
#include "gtkstylecontextprivate.h"
52
#include "gtkprivate.h"
53
#include "gdkpixbufutilsprivate.h"
54 55 56 57 58 59

#undef GDK_DEPRECATED
#undef GDK_DEPRECATED_FOR
#define GDK_DEPRECATED
#define GDK_DEPRECATED_FOR(f)

60
#include "deprecated/gtkstyle.h"
61

62 63 64 65
/* this is in case round() is not provided by the compiler, 
 * such as in the case of C89 compilers, like MSVC
 */
#include "fallback-c89.c"
66 67 68 69 70 71 72 73 74

/**
 * SECTION:gtkicontheme
 * @Short_description: Looking up icons by name
 * @Title: GtkIconTheme
 *
 * #GtkIconTheme provides a facility for looking up icons by name
 * and size. The main reason for using a name rather than simply
 * providing a filename is to allow different icons to be used
75
 * depending on what “icon theme” is selected
76
 * by the user. The operation of icon themes on Linux and Unix
77
 * follows the [Icon Theme Specification](http://www.freedesktop.org/Standards/icon-theme-spec)
78 79 80
 * There is a fallback icon theme, named `hicolor`, where applications
 * should install their icons, but additional icon themes can be installed
 * as operating system vendors and users choose.
81
 *
82 83
 * Named icons are similar to the deprecated [Stock Items][gtkstock],
 * and the distinction between the two may be a bit confusing.
84
 * A few things to keep in mind:
85 86
 * 
 * - Stock images usually are used in conjunction with
87
 *   [Stock Items][gtkstock], such as %GTK_STOCK_OK or
88 89 90 91 92 93 94 95 96 97 98 99 100 101
 *   %GTK_STOCK_OPEN. Named icons are easier to set up and therefore
 *   are more useful for new icons that an application wants to
 *   add, such as application icons or window icons.
 * 
 * - Stock images can only be loaded at the symbolic sizes defined
 *   by the #GtkIconSize enumeration, or by custom sizes defined
 *   by gtk_icon_size_register(), while named icons are more flexible
 *   and any pixel size can be specified.
 * 
 * - Because stock images are closely tied to stock items, and thus
 *   to actions in the user interface, stock images may come in
 *   multiple variants for different widget states or writing
 *   directions.
 *
102 103 104 105 106 107
 * A good rule of thumb is that if there is a stock image for what
 * you want to use, use it, otherwise use a named icon. It turns
 * out that internally stock images are generally defined in
 * terms of one or more named icons. (An example of the
 * more than one case is icons that depend on writing direction;
 * %GTK_STOCK_GO_FORWARD uses the two themed icons
William Jon McCann's avatar
William Jon McCann committed
108
 * “gtk-stock-go-forward-ltr” and “gtk-stock-go-forward-rtl”.)
109 110 111 112 113
 *
 * In many cases, named themes are used indirectly, via #GtkImage
 * or stock items, rather than directly, but looking up icons
 * directly is also simple. The #GtkIconTheme object acts
 * as a database of all the icons in the current theme. You
114
 * can create new #GtkIconTheme objects, but it’s much more
115 116
 * efficient to use the standard icon theme for the #GdkScreen
 * so that the icon information is shared with other people
117
 * looking up icons.
118
 * |[<!-- language="C" -->
119 120 121 122 123 124
 * GError *error = NULL;
 * GtkIconTheme *icon_theme;
 * GdkPixbuf *pixbuf;
 *
 * icon_theme = gtk_icon_theme_get_default ();
 * pixbuf = gtk_icon_theme_load_icon (icon_theme,
125 126 127
 *                                    "my-icon-name", // icon name
 *                                    48, // icon size
 *                                    0,  // flags
128 129 130
 *                                    &error);
 * if (!pixbuf)
 *   {
131
 *     g_warning ("Couldn’t load icon: %s", error->message);
132 133 134 135
 *     g_error_free (error);
 *   }
 * else
 *   {
136
 *     // Use the pixbuf
137 138
 *     g_object_unref (pixbuf);
 *   }
139
 * ]|
140 141
 */

142
#define FALLBACK_ICON_THEME "hicolor"
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

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,
158
  ICON_SUFFIX_PNG = 1 << 2,
159 160
  HAS_ICON_FILE = 1 << 3,
  ICON_SUFFIX_SYMBOLIC_PNG = 1 << 4
161 162
} IconSuffix;

Alexander Larsson's avatar
Alexander Larsson committed
163 164 165 166 167 168
#define INFO_CACHE_LRU_SIZE 32
#if 0
#define DEBUG_CACHE(args) g_print args
#else
#define DEBUG_CACHE(args)
#endif
169 170 171

struct _GtkIconThemePrivate
{
Alexander Larsson's avatar
Alexander Larsson committed
172 173 174
  GHashTable *info_cache;
  GList *info_cache_lru;

175 176 177
  gchar *current_theme;
  gchar **search_path;
  gint search_path_len;
178
  GList *resource_paths;
179

180
  guint custom_theme        : 1;
181 182
  guint is_screen_singleton : 1;
  guint pixbuf_supports_svg : 1;
183
  guint themes_valid        : 1;
184
  guint loading_themes      : 1;
185 186 187 188 189 190

  /* A list of all the themes needed to look up icons.
   * In search order, without duplicates
   */
  GList *themes;
  GHashTable *unthemed_icons;
191

192
  /* GdkScreen for the icon theme (may be NULL) */
193
  GdkScreen *screen;
194

195
  /* time when we last stat:ed for theme changes */
196
  glong last_stat_time;
197
  GList *dir_mtimes;
198

199
  gulong theme_changed_idle;
200 201
};

Alexander Larsson's avatar
Alexander Larsson committed
202 203 204
typedef struct {
  gchar **icon_names;
  gint size;
205
  gint scale;
Alexander Larsson's avatar
Alexander Larsson committed
206 207 208
  GtkIconLookupFlags flags;
} IconInfoKey;

209 210 211 212 213 214 215 216 217 218 219 220
typedef struct _SymbolicPixbufCache SymbolicPixbufCache;

struct _SymbolicPixbufCache {
  GdkPixbuf *pixbuf;
  GdkPixbuf *proxy_pixbuf;
  GdkRGBA  fg;
  GdkRGBA  success_color;
  GdkRGBA  warning_color;
  GdkRGBA  error_color;
  SymbolicPixbufCache *next;
};

221 222 223 224 225
struct _GtkIconInfoClass
{
  GObjectClass parent_class;
};

226 227
struct _GtkIconInfo
{
228 229
  GObject parent_instance;

230 231
  /* Information about the source
   */
Alexander Larsson's avatar
Alexander Larsson committed
232 233 234
  IconInfoKey key;
  GtkIconTheme *in_cache;

235
  gchar *filename;
236
  GFile *icon_file;
237
  GLoadableIcon *loadable;
238
  GSList *emblem_infos;
239

Anders Carlsson's avatar
Anders Carlsson committed
240 241 242
  /* Cache pixbuf (if there is any) */
  GdkPixbuf *cache_pixbuf;

243 244 245 246 247
  /* Information about the directory where
   * the source was found
   */
  IconThemeDirType dir_type;
  gint dir_size;
248
  gint dir_scale;
249 250
  gint min_size;
  gint max_size;
251 252 253 254

  /* Parameters influencing the scaled icon
   */
  gint desired_size;
255
  gint desired_scale;
256
  guint forced_size     : 1;
257
  guint emblems_applied : 1;
258
  guint is_svg          : 1;
259
  guint is_resource     : 1;
260

261 262 263 264
  /* Cached information if we go ahead and try to load
   * the icon.
   */
  GdkPixbuf *pixbuf;
Alexander Larsson's avatar
Alexander Larsson committed
265
  GdkPixbuf *proxy_pixbuf;
266
  GError *load_error;
267
  gdouble unscaled_scale;
268
  gdouble scale;
269

270
  SymbolicPixbufCache *symbolic_pixbuf_cache;
271

272 273
  gint symbolic_width;
  gint symbolic_height;
274 275 276 277
};

typedef struct
{
Matthias Clasen's avatar
Matthias Clasen committed
278 279 280 281
  gchar *name;
  gchar *display_name;
  gchar *comment;
  gchar *example;
282 283 284 285 286 287 288 289 290 291

  /* In search order */
  GList *dirs;
} IconTheme;

typedef struct
{
  IconThemeDirType type;
  GQuark context;

Matthias Clasen's avatar
Matthias Clasen committed
292 293 294 295 296
  gint size;
  gint min_size;
  gint max_size;
  gint threshold;
  gint scale;
297
  gboolean is_resource;
298

Matthias Clasen's avatar
Matthias Clasen committed
299 300 301
  gchar *dir;
  gchar *subdir;
  gint subdir_index;
302 303
  
  GtkIconCache *cache;
304 305 306 307 308 309
  
  GHashTable *icons;
} IconThemeDir;

typedef struct
{
Matthias Clasen's avatar
Matthias Clasen committed
310 311
  gchar *svg_filename;
  gchar *no_svg_filename;
312
  gboolean is_resource;
313 314 315 316 317 318 319 320 321 322
} UnthemedIcon;

typedef struct
{
  gint size;
  GdkPixbuf *pixbuf;
} BuiltinIcon;

typedef struct 
{
Matthias Clasen's avatar
Matthias Clasen committed
323
  gchar *dir;
324
  time_t mtime;
325
  GtkIconCache *cache;
326
  gboolean exists;
327 328
} IconThemeDirMtime;

Matthias Clasen's avatar
Matthias Clasen committed
329 330 331 332 333 334 335 336 337 338 339 340
static void         gtk_icon_theme_finalize   (GObject          *object);
static void         theme_dir_destroy         (IconThemeDir     *dir);
static void         theme_destroy              (IconTheme       *theme);
static GtkIconInfo *theme_lookup_icon         (IconTheme        *theme,
                                               const gchar      *icon_name,
                                               gint              size,
                                               gint              scale,
                                               gboolean          allow_svg,
                                               gboolean          use_default_icons);
static void         theme_list_icons          (IconTheme        *theme,
                                               GHashTable       *icons,
                                               GQuark            context);
341 342
static gboolean     theme_has_icon            (IconTheme        *theme,
                                               const gchar      *icon_name);
Matthias Clasen's avatar
Matthias Clasen committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
static void         theme_list_contexts       (IconTheme        *theme,
                                               GHashTable       *contexts);
static void         theme_subdir_load         (GtkIconTheme     *icon_theme,
                                               IconTheme        *theme,
                                               GKeyFile         *theme_file,
                                               gchar            *subdir);
static void         do_theme_change           (GtkIconTheme     *icon_theme);
static void         blow_themes               (GtkIconTheme     *icon_themes);
static gboolean     rescan_themes             (GtkIconTheme     *icon_themes);
static IconSuffix   theme_dir_get_icon_suffix (IconThemeDir     *dir,
                                               const gchar      *icon_name,
                                               gboolean         *has_icon_file);
static GtkIconInfo *icon_info_new             (IconThemeDirType  type,
                                               gint              dir_size,
                                               gint              dir_scale);
static GtkIconInfo *icon_info_new_builtin     (BuiltinIcon      *icon);
static IconSuffix   suffix_from_name          (const gchar      *name);
static BuiltinIcon *find_builtin_icon         (const gchar      *icon_name,
                                               gint              size,
                                               gint              scale,
                                               gint             *min_difference_p);
static void         remove_from_lru_cache     (GtkIconTheme     *icon_theme,
                                               GtkIconInfo      *icon_info);
366
static gboolean     icon_info_ensure_scale_and_pixbuf (GtkIconInfo* icon_info);
367 368 369 370 371

static guint signal_changed = 0;

static GHashTable *icon_theme_builtin_icons;

Alexander Larsson's avatar
Alexander Larsson committed
372 373 374 375 376 377 378 379 380 381
static guint
icon_info_key_hash (gconstpointer _key)
{
  const IconInfoKey *key = _key;
  guint h = 0;
  int i;
  for (i = 0; key->icon_names[i] != NULL; i++)
    h ^= g_str_hash (key->icon_names[i]);

  h ^= key->size * 0x10001;
382 383
  h ^= key->scale * 0x1000010;
  h ^= key->flags * 0x100000100;
Alexander Larsson's avatar
Alexander Larsson committed
384 385 386 387 388

  return h;
}

static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
389 390
icon_info_key_equal (gconstpointer _a,
                     gconstpointer _b)
Alexander Larsson's avatar
Alexander Larsson committed
391 392 393 394 395 396 397 398
{
  const IconInfoKey *a = _a;
  const IconInfoKey *b = _b;
  int i;

  if (a->size != b->size)
    return FALSE;

399 400 401
  if (a->scale != b->scale)
    return FALSE;

Alexander Larsson's avatar
Alexander Larsson committed
402 403 404 405 406 407 408 409
  if (a->flags != b->flags)
    return FALSE;

  for (i = 0;
       a->icon_names[i] != NULL &&
       b->icon_names[i] != NULL; i++)
    {
      if (strcmp (a->icon_names[i], b->icon_names[i]) != 0)
Matthias Clasen's avatar
Matthias Clasen committed
410
        return FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
411 412 413 414 415
    }

  return a->icon_names[i] == NULL && b->icon_names[i] == NULL;
}

416
G_DEFINE_TYPE_WITH_PRIVATE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT)
417 418 419 420 421 422

/**
 * 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.
423
 * Usually, you’ll want to use gtk_icon_theme_get_default()
424 425 426
 * or gtk_icon_theme_get_for_screen() rather than creating
 * a new icon theme object for scratch.
 * 
427
 * Returns: the newly created #GtkIconTheme object.
Matthias Clasen's avatar
Matthias Clasen committed
428 429
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
430
 */
431 432 433 434 435 436 437 438 439 440 441
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().
442
 *
443
 * Returns: (transfer none): A unique #GtkIconTheme associated with
444 445 446
 *     the default screen. This icon theme is associated with
 *     the screen and can be used as long as the screen
 *     is open. Do not ref or unref it.
Matthias Clasen's avatar
Matthias Clasen committed
447 448
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
449
 */
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
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.
468
 *
469
 * Returns: (transfer none): A unique #GtkIconTheme associated with
470 471
 *  the given screen. This icon theme is associated with
 *  the screen and can be used as long as the screen
472
 *  is open. Do not ref or unref it.
Matthias Clasen's avatar
Matthias Clasen committed
473 474
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
475
 */
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
GtkIconTheme *
gtk_icon_theme_get_for_screen (GdkScreen *screen)
{
  GtkIconTheme *icon_theme;

  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

  icon_theme = g_object_get_data (G_OBJECT (screen), "gtk-icon-theme");
  if (!icon_theme)
    {
      GtkIconThemePrivate *priv;

      icon_theme = gtk_icon_theme_new ();
      gtk_icon_theme_set_screen (icon_theme, screen);

      priv = icon_theme->priv;
      priv->is_screen_singleton = TRUE;

Matthias Clasen's avatar
Matthias Clasen committed
494
      g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), icon_theme);
495 496 497 498 499 500 501 502 503 504 505 506
    }

  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;

Matthias Clasen's avatar
Matthias Clasen committed
507 508 509 510 511 512 513 514
  /**
   * 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.
   */
Matthias Clasen's avatar
Matthias Clasen committed
515
  signal_changed = g_signal_new (I_("changed"),
Matthias Clasen's avatar
Matthias Clasen committed
516 517 518 519
                                 G_TYPE_FROM_CLASS (klass),
                                 G_SIGNAL_RUN_LAST,
                                 G_STRUCT_OFFSET (GtkIconThemeClass, changed),
                                 NULL, NULL,
520
                                 NULL,
Matthias Clasen's avatar
Matthias Clasen committed
521
                                 G_TYPE_NONE, 0);
522 523 524
}


Matthias Clasen's avatar
Matthias Clasen committed
525 526
/* Callback when the display that the icon theme is attached
 * to is closed; unset the screen, and if it’s the unique theme
527 528 529 530
 * for the screen, drop the reference
 */
static void
display_closed (GdkDisplay   *display,
Matthias Clasen's avatar
Matthias Clasen committed
531 532
                gboolean      is_error,
                GtkIconTheme *icon_theme)
533 534 535 536 537 538 539
{
  GtkIconThemePrivate *priv = icon_theme->priv;
  GdkScreen *screen = priv->screen;
  gboolean was_screen_singleton = priv->is_screen_singleton;

  if (was_screen_singleton)
    {
Matthias Clasen's avatar
Matthias Clasen committed
540
      g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), NULL);
541 542 543 544 545 546 547 548 549 550 551 552 553 554
      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)
{
555 556 557
#define theme_changed(_old, _new) \
  ((_old && !_new) || (!_old && _new) || \
   (_old && _new && strcmp (_old, _new) != 0))
558 559 560 561 562
  GtkIconThemePrivate *priv = icon_theme->priv;

  if (!priv->custom_theme)
    {
      gchar *theme = NULL;
563
      gboolean changed = FALSE;
564 565

      if (priv->screen)
Matthias Clasen's avatar
Matthias Clasen committed
566 567 568 569
        {
          GtkSettings *settings = gtk_settings_get_for_screen (priv->screen);
          g_object_get (settings, "gtk-icon-theme-name", &theme, NULL);
        }
570

571
      if (theme_changed (priv->current_theme, theme))
Matthias Clasen's avatar
Matthias Clasen committed
572 573 574 575 576
        {
          g_free (priv->current_theme);
          priv->current_theme = theme;
          changed = TRUE;
        }
577
      else
Matthias Clasen's avatar
Matthias Clasen committed
578
        g_free (theme);
579 580

      if (changed)
Matthias Clasen's avatar
Matthias Clasen committed
581
        do_theme_change (icon_theme);
582
    }
583
#undef theme_changed
584 585 586 587 588 589
}

/* Callback when the icon theme GtkSetting changes
 */
static void
theme_changed (GtkSettings  *settings,
Matthias Clasen's avatar
Matthias Clasen committed
590 591
               GParamSpec   *pspec,
               GtkIconTheme *icon_theme)
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
{
  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,
Matthias Clasen's avatar
Matthias Clasen committed
609 610
                                            (gpointer) display_closed,
                                            icon_theme);
611
      g_signal_handlers_disconnect_by_func (settings,
Matthias Clasen's avatar
Matthias Clasen committed
612 613
                                            (gpointer) theme_changed,
                                            icon_theme);
614 615 616 617 618 619 620 621 622 623 624

      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
625
 * to track the user’s currently configured icon theme,
626
 * which might be different for different screens.
Matthias Clasen's avatar
Matthias Clasen committed
627 628
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
629
 */
630 631
void
gtk_icon_theme_set_screen (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
632
                           GdkScreen    *screen)
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
{
  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",
Matthias Clasen's avatar
Matthias Clasen committed
653
                        G_CALLBACK (display_closed), icon_theme);
654
      g_signal_connect (settings, "notify::gtk-icon-theme-name",
Matthias Clasen's avatar
Matthias Clasen committed
655
                        G_CALLBACK (theme_changed), icon_theme);
656 657 658 659 660 661 662 663 664
    }

  update_current_theme (icon_theme);
}

/* Checks whether a loader for SVG files has been registered
 * with GdkPixbuf.
 */
static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
665
pixbuf_supports_svg (void)
666
{
667
  GSList *formats;
668
  GSList *tmp_list;
Matthias Clasen's avatar
Matthias Clasen committed
669
  static gint found_svg = -1;
670

Matthias Clasen's avatar
Matthias Clasen committed
671
  if (found_svg != -1)
672
    return found_svg;
673 674 675

  formats = gdk_pixbuf_get_formats ();

Matthias Clasen's avatar
Matthias Clasen committed
676
  found_svg = FALSE; 
677 678 679 680 681 682
  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++)
Matthias Clasen's avatar
Matthias Clasen committed
683 684 685 686
        {
          if (strcmp (*mime_type, "image/svg") == 0)
            found_svg = TRUE;
        }
687

688
      g_strfreev (mime_types);
689 690 691
    }

  g_slist_free (formats);
692
  
693 694 695
  return found_svg;
}

Alexander Larsson's avatar
Alexander Larsson committed
696 697 698 699 700 701 702
/* The icon info was removed from the icon_info_hash hash table */
static void
icon_info_uncached (GtkIconInfo *icon_info)
{
  GtkIconTheme *icon_theme = icon_info->in_cache;

  DEBUG_CACHE (("removing %p (%s %d 0x%x) from cache (icon_them: %p)  (cache size %d)\n",
Matthias Clasen's avatar
Matthias Clasen committed
703 704 705 706 707
                icon_info,
                g_strjoinv (",", icon_info->key.icon_names),
                icon_info->key.size, icon_info->key.flags,
                icon_theme,
                icon_theme != NULL ? g_hash_table_size (icon_theme->priv->info_cache) : 0));
Alexander Larsson's avatar
Alexander Larsson committed
708 709 710 711 712 713 714

  icon_info->in_cache = NULL;

  if (icon_theme != NULL)
    remove_from_lru_cache (icon_theme, icon_info);
}

715 716 717 718
static void
gtk_icon_theme_init (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv;
719
  const gchar * const *xdg_data_dirs;
720
  int i, j;
721

722
  priv = gtk_icon_theme_get_instance_private (icon_theme);
723 724
  icon_theme->priv = priv;

Alexander Larsson's avatar
Alexander Larsson committed
725
  priv->info_cache = g_hash_table_new_full (icon_info_key_hash, icon_info_key_equal, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
726
                                            (GDestroyNotify)icon_info_uncached);
Alexander Larsson's avatar
Alexander Larsson committed
727

728
  priv->custom_theme = FALSE;
729 730 731 732

  xdg_data_dirs = g_get_system_data_dirs ();
  for (i = 0; xdg_data_dirs[i]; i++) ;

733
  priv->search_path_len = 2 * i + 2;
734 735 736 737 738
  
  priv->search_path = g_new (char *, priv->search_path_len);
  
  i = 0;
  priv->search_path[i++] = g_build_filename (g_get_user_data_dir (), "icons", NULL);
739
  priv->search_path[i++] = g_build_filename (g_get_home_dir (), ".icons", NULL);
740
  
741 742
  for (j = 0; xdg_data_dirs[j]; j++) 
    priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "icons", NULL);
743

744 745
  for (j = 0; xdg_data_dirs[j]; j++) 
    priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "pixmaps", NULL);
746

747
  priv->resource_paths = g_list_append (NULL, g_strdup ("/org/gtk/libgtk/icons/"));
748

749 750 751
  priv->themes_valid = FALSE;
  priv->themes = NULL;
  priv->unthemed_icons = NULL;
752
  
753 754 755 756 757 758
  priv->pixbuf_supports_svg = pixbuf_supports_svg ();
}

static void
free_dir_mtime (IconThemeDirMtime *dir_mtime)
{
759 760 761
  if (dir_mtime->cache)
    _gtk_icon_cache_unref (dir_mtime->cache);

762
  g_free (dir_mtime->dir);
Matthias Clasen's avatar
Matthias Clasen committed
763
  g_slice_free (IconThemeDirMtime, dir_mtime);
764 765
}

766
static gboolean
767
theme_changed_idle (gpointer user_data)
768 769 770 771 772 773 774
{
  GtkIconTheme *icon_theme;
  GtkIconThemePrivate *priv;

  icon_theme = GTK_ICON_THEME (user_data);
  priv = icon_theme->priv;

775 776
  g_signal_emit (icon_theme, signal_changed, 0);

777
  if (priv->screen && priv->is_screen_singleton)
778
    gtk_style_context_reset_widgets (priv->screen);
779

780
  priv->theme_changed_idle = 0;
781 782 783 784

  return FALSE;
}

785 786 787 788 789 790
static void
queue_theme_changed (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;

  if (!priv->theme_changed_idle)
791 792 793 794 795 796
    {
      priv->theme_changed_idle =
        gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE - 2,
                                   theme_changed_idle, icon_theme, NULL);
      g_source_set_name_by_id (priv->theme_changed_idle, "[gtk+] theme_changed_idle");
    }
797 798
}

799 800 801 802
static void
do_theme_change (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
803

Alexander Larsson's avatar
Alexander Larsson committed
804 805
  g_hash_table_remove_all (priv->info_cache);

806 807
  if (!priv->themes_valid)
    return;
808

809 810
  GTK_NOTE (ICONTHEME,
            g_message ("change to icon theme \"%s\"", priv->current_theme));
811
  blow_themes (icon_theme);
812

813 814
  queue_theme_changed (icon_theme);

815 816 817 818 819 820 821 822 823
}

static void
blow_themes (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
  
  if (priv->themes_valid)
    {
824 825
      g_list_free_full (priv->themes, (GDestroyNotify) theme_destroy);
      g_list_free_full (priv->dir_mtimes, (GDestroyNotify) free_dir_mtime);
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
      g_hash_table_destroy (priv->unthemed_icons);
    }
  priv->themes = NULL;
  priv->unthemed_icons = NULL;
  priv->dir_mtimes = NULL;
  priv->themes_valid = FALSE;
}

static void
gtk_icon_theme_finalize (GObject *object)
{
  GtkIconTheme *icon_theme;
  GtkIconThemePrivate *priv;
  int i;

  icon_theme = GTK_ICON_THEME (object);
  priv = icon_theme->priv;

Alexander Larsson's avatar
Alexander Larsson committed
844 845 846
  g_hash_table_destroy (priv->info_cache);
  g_assert (priv->info_cache_lru == NULL);

847
  if (priv->theme_changed_idle)
848
    g_source_remove (priv->theme_changed_idle);
849

850 851 852 853
  unset_screen (icon_theme);

  g_free (priv->current_theme);

Matthias Clasen's avatar
Matthias Clasen committed
854
  for (i = 0; i < priv->search_path_len; i++)
855 856
    g_free (priv->search_path[i]);
  g_free (priv->search_path);
857 858

  g_list_free_full (priv->resource_paths, g_free);
859 860

  blow_themes (icon_theme);
861

Matthias Clasen's avatar
Matthias Clasen committed
862
  G_OBJECT_CLASS (gtk_icon_theme_parent_class)->finalize (object);  
863 864 865 866 867
}

/**
 * gtk_icon_theme_set_search_path:
 * @icon_theme: a #GtkIconTheme
868 869
 * @path: (array length=n_elements) (element-type filename): array of
 *     directories that are searched for icon themes
870 871 872 873 874
 * @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
875 876 877
 * as the icon theme containing an index.theme file. (Themes from
 * multiple of the path elements are combined to allow themes to be
 * extended by adding icons in the user’s home directory.)
878
 *
879
 * In addition if an icon found isn’t found either in the current
880 881 882 883
 * 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
884
 * into the fallback icon theme, which is called hicolor,
885
 * rather than directly on the icon path.)
Matthias Clasen's avatar
Matthias Clasen committed
886 887
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
888
 */
889 890
void
gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
891 892
                                const gchar  *path[],
                                gint          n_elements)
893 894 895 896 897 898 899 900 901 902 903 904 905 906
{
  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;
Matthias Clasen's avatar
Matthias Clasen committed
907

908 909 910 911 912 913 914 915 916
  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
917
 * @path: (allow-none) (array length=n_elements) (element-type filename) (out):
918 919 920 921
 *     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
 *
922
 * Gets the current search path. See gtk_icon_theme_set_search_path().
Matthias Clasen's avatar
Matthias Clasen committed
923 924
 *
 * Since: 2.4
925
 */
926
void
927 928 929
gtk_icon_theme_get_search_path (GtkIconTheme  *icon_theme,
                                gchar        **path[],
                                gint          *n_elements)
930 931
{
  GtkIconThemePrivate *priv;
Matthias Clasen's avatar
Matthias Clasen committed
932
  gint i;
933 934 935 936 937 938 939 940 941 942 943 944

  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++)
Matthias Clasen's avatar
Matthias Clasen committed
945
        (*path)[i] = g_strdup (priv->search_path[i]);
946 947 948 949 950 951 952
      (*path)[i] = NULL;
    }
}

/**
 * gtk_icon_theme_append_search_path:
 * @icon_theme: a #GtkIconTheme
953
 * @path: (type filename): directory name to append to the icon path
954
 * 
955 956
 * Appends a directory to the search path. 
 * See gtk_icon_theme_set_search_path(). 
Matthias Clasen's avatar
Matthias Clasen committed
957 958
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
959
 */
960 961
void
gtk_icon_theme_append_search_path (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
962
                                   const gchar  *path)
963 964 965 966 967 968 969 970 971
{
  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++;
Matthias Clasen's avatar
Matthias Clasen committed
972

973 974 975 976 977 978 979 980 981
  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
982
 * @path: (type filename): directory name to prepend to the icon path
983
 * 
984 985
 * Prepends a directory to the search path. 
 * See gtk_icon_theme_set_search_path().
Matthias Clasen's avatar
Matthias Clasen committed
986 987
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
988
 */
989 990
void
gtk_icon_theme_prepend_search_path (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
991
                                    const gchar  *path)
992 993
{
  GtkIconThemePrivate *priv;
Matthias Clasen's avatar
Matthias Clasen committed
994
  gint i;
995 996 997 998 999 1000 1001 1002 1003

  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);

1004 1005
  for (i = priv->search_path_len - 1; i > 0; i--)
    priv->search_path[i] = priv->search_path[i - 1];
1006 1007 1008 1009 1010 1011
  
  priv->search_path[0] = g_strdup (path);

  do_theme_change (icon_theme);
}

1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
/**
 * gtk_icon_theme_add_resource_path:
 * @icon_theme: a #GtkIconTheme
 * @path: a resource path
 *
 * Adds a resource path that will be looked at when looking
 * for icons, similar to search paths.
 *
 * This function should be used to make application-specific icons
 * available as part of the icon theme.
 *
 * The resources are considered as part of the hicolor icon theme
 * and must be located in subdirectories that are defined in the
 * hicolor icon theme, such as `@path/16x16/actions/run.png`.
 * Icons that are directly placed in the resource path instead
 * of a subdirectory are also considered as ultimate fallback.
 *
 * Since: 3.14
 */
1031 1032 1033 1034
void
gtk_icon_theme_add_resource_path (GtkIconTheme *icon_theme,
                                  const gchar  *path)
{
1035
  GtkIconThemePrivate *priv = NULL;
1036 1037 1038 1039

  g_return_if_fail (GTK_IS_ICON_THEME (icon_theme));
  g_return_if_fail (path != NULL);

1040
  priv = icon_theme->priv;
1041 1042 1043 1044 1045
  priv->resource_paths = g_list_append (priv->resource_paths, g_strdup (path));

  do_theme_change (icon_theme);
}

1046 1047 1048
/**
 * gtk_icon_theme_set_custom_theme:
 * @icon_theme: a #GtkIconTheme
1049 1050
 * @theme_name: (allow-none): name of icon theme to use instead of
 *   configured theme, or %NULL to unset a previously set custom theme
1051 1052 1053 1054
 * 
 * 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()
1055
 * and gtk_icon_theme_get_for_screen().
Matthias Clasen's avatar
Matthias Clasen committed
1056 1057
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
1058
 */
1059 1060
void
gtk_icon_theme_set_custom_theme (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
1061
                                 const gchar  *theme_name)
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073
{
  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;
1074
      if (!priv->current_theme || strcmp (theme_name, priv->current_theme) != 0)
Matthias Clasen's avatar
Matthias Clasen committed
1075 1076 1077
        {
          g_free (priv->current_theme);
          priv->current_theme = g_strdup (theme_name);
1078

Matthias Clasen's avatar
Matthias Clasen committed
1079 1080
          do_theme_change (icon_theme);
        }
1081 1082 1083
    }
  else
    {
1084
      if (priv->custom_theme)
Matthias Clasen's avatar
Matthias Clasen committed
1085 1086 1087 1088
        {
          priv->custom_theme = FALSE;
          update_current_theme (icon_theme);
        }
1089 1090 1091
    }
}

1092 1093 1094 1095
static const gchar builtin_hicolor_index[] =
"[Icon Theme]\n"
"Name=Hicolor\n"
"Hidden=True\n"
1096
"Directories=16x16/actions,16x16/status,22x22/actions,24x24/actions,24x24/status,32x32/actions,32x32/status,48x48/status,64x64/actions\n"
1097 1098 1099
"[16x16/actions]\n"
"Size=16\n"
"Type=Threshold\n"
1100 1101 1102
"[16x16/status]\n"
"Size=16\n"
"Type=Threshold\n"
1103 1104 1105 1106 1107 1108
"[22x22/actions]\n"
"Size=22\n"
"Type=Threshold\n"
"[24x24/actions]\n"
"Size=24\n"
"Type=Threshold\n"
1109 1110 1111
"[24x24/status]\n"
"Size=24\n"
"Type=Threshold\n"
1112 1113
"[32x32/actions]\n"
"Size=32\n"
1114 1115 1116 1117 1118 1119 1120 1121 1122
"Type=Threshold\n"
"[32x32/status]\n"
"Size=32\n"
"Type=Threshold\n"
"[48x48/status]\n"
"Size=48\n"
"Type=Threshold\n"
"[64x64/actions]\n"
"Size=64\n"
1123 1124
"Type=Threshold\n";

1125
static void
Matthias Clasen's avatar
Matthias Clasen committed
1126 1127
insert_theme (GtkIconTheme *icon_theme,
              const gchar  *theme_name)
1128
{
Matthias Clasen's avatar
Matthias Clasen committed
1129
  gint i;
1130
  GList *l;
Matthias Clasen's avatar
Matthias Clasen committed
1131 1132 1133
  gchar **dirs;
  gchar **scaled_dirs;
  gchar **themes;
1134
  GtkIconThemePrivate *priv;
1135
  IconTheme *theme = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1136
  gchar *path;
1137 1138
  GKeyFile *theme_file;
  GError *error = NULL;
1139
  IconThemeDirMtime *dir_mtime;
1140
  GStatBuf stat_buf;
1141 1142
  
  priv = icon_theme->priv;
1143

1144 1145 1146 1147
  for (l = priv->themes; l != NULL; l = l->next)
    {
      theme = l->data;
      if (strcmp (theme->name, theme_name) == 0)
Matthias Clasen's avatar
Matthias Clasen committed
1148
        return;
1149 1150 1151 1152 1153
    }
  
  for (i = 0; i < priv->search_path_len; i++)
    {
      path = g_build_filename (priv->search_path[i],
Matthias Clasen's avatar
Matthias Clasen committed
1154 1155
                               theme_name,
                               NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1156
      dir_mtime = g_slice_new (IconThemeDirMtime);
1157
      dir_mtime->cache = NULL;
1158
      dir_mtime->dir = path;
1159
      if (g_stat (path, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode)) {
Matthias Clasen's avatar
Matthias Clasen committed
1160
        dir_mtime->mtime = stat_buf.st_mtime;
1161 1162
        dir_mtime->exists = TRUE;
      } else {
Matthias Clasen's avatar
Matthias Clasen committed
1163
        dir_mtime->mtime = 0;
1164 1165
        dir_mtime->exists = FALSE;
      }
1166 1167 1168 1169 1170

      priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime);
    }

  theme_file = NULL;
1171
  for (i = 0; i < priv->search_path_len && !theme_file; i++)
1172 1173
    {
      path = g_build_filename (priv->search_path[i],
Matthias Clasen's avatar
Matthias Clasen committed
1174 1175 1176
                               theme_name,
                               "index.theme",
                               NULL);
1177
      if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) 
Matthias Clasen's avatar
Matthias Clasen committed
1178 1179 1180
        {
          theme_file = g_key_file_new ();
          g_key_file_set_list_separator (theme_file, ',');
1181
          if (!g_key_file_load_from_file (theme_file, path, 0, &error))
Matthias Clasen's avatar
Matthias Clasen committed
1182 1183 1184 1185 1186 1187 1188
            {
              g_key_file_free (theme_file);
              theme_file = NULL;
              g_error_free (error);
              error = NULL;
            }
        }
1189 1190 1191
      g_free (path);
    }

1192
  if (theme_file || strcmp (theme_name, FALLBACK_ICON_THEME) == 0)
1193 1194 1195 1196
    {
      theme = g_new0 (IconTheme, 1);
      theme->name = g_strdup (theme_name);
      priv->themes = g_list_prepend (priv->themes, theme);
1197 1198 1199 1200 1201 1202
      if (!theme_file)
        {
          theme_file = g_key_file_new ();
          g_key_file_set_list_separator (theme_file, ',');
          g_key_file_load_from_data (theme_file, builtin_hicolor_index, -1, 0, NULL);
        }
1203 1204
    }

1205 1206
  if (theme_file == NULL)
    return;
1207

1208 1209 1210
  theme->display_name = 
    g_key_file_get_locale_string (theme_file, "Icon Theme", "Name", NULL, NULL);
  if (!theme->display_name)
1211
    g_warning ("Theme file for %s has no name", theme_name);
1212

1213 1214
  dirs = g_key_file_get_string_list (theme_file, "Icon Theme", "Directories", NULL, NULL);
  if (!dirs)
Matthias Clasen's avatar
Matthias Clasen committed
1215
    {
1216
      g_warning ("Theme file for %s has no directories", theme_name);
1217 1218
      priv->themes = g_list_remove (priv->themes, theme);
      g_free (theme->name);
Matthias Clasen's avatar
Matthias Clasen committed
1219 1220 1221 1222 1223
      g_free (theme->display_name);
      g_free (theme);
      g_key_file_free (theme_file);
      return;
    }
1224 1225 1226

  scaled_dirs = g_key_file_get_string_list (theme_file, "Icon Theme", "ScaledDirectories", NULL, NULL);

1227 1228
  theme->comment = 
    g_key_file_get_locale_string (theme_file, 
Matthias Clasen's avatar
Matthias Clasen committed
1229 1230
                                  "Icon Theme", "Comment",
                                  NULL, NULL);
1231 1232
  theme->example = 
    g_key_file_get_string (theme_file, 
Matthias Clasen's avatar
Matthias Clasen committed
1233 1234
                           "Icon Theme", "Example",
                           NULL);
1235 1236

  theme->dirs = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1237 1238 1239
  for (i = 0; dirs[i] != NULL; i++)
    theme_subdir_load (icon_theme, theme, theme_file, dirs[i]);

1240 1241 1242 1243 1244
  if (scaled_dirs)
    {
      for (i = 0; scaled_dirs[i] != NULL; i++)
        theme_subdir_load (icon_theme, theme, theme_file, scaled_dirs[i]);
    }
Matthias Clasen's avatar
Matthias Clasen committed
1245
  g_strfreev (dirs);
1246
  g_strfreev (scaled_dirs);
Matthias Clasen's avatar
Matthias Clasen committed
1247

1248 1249
  theme->dirs = g_list_reverse (theme->dirs);

1250
  themes = g_key_file_get_string_list (theme_file,
Matthias Clasen's avatar
Matthias Clasen committed
1251 1252 1253 1254
                                       "Icon Theme",
                                       "Inherits",
                                       NULL,
                                       NULL);
1255
  if (themes)
1256 1257
    {
      for (i = 0; themes[i] != NULL; i++)
Matthias Clasen's avatar
Matthias Clasen committed
1258
        insert_theme (icon_theme, themes[i]);
1259 1260 1261 1262
      
      g_strfreev (themes);
    }

1263