gtkicontheme.c 165 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 36 37
#define WIN32_MEAN_AND_LEAN
#include <windows.h>
#include "win32/gdkwin32.h"
38 39
#endif /* G_OS_WIN32 */

40
#include "gtkicontheme.h"
41
#include "gtkdebug.h"
42
#include "deprecated/gtkiconfactory.h"
43
#include "gtkiconcache.h"
44
#include "gtkintl.h"
45
#include "gtkmain.h"
46
#include "deprecated/gtknumerableiconprivate.h"
47
#include "gtksettingsprivate.h"
48
#include "gtkprivate.h"
49 50 51 52 53 54

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

55
#include "deprecated/gtkstyle.h"
56

57 58 59 60
/* 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"
61 62 63 64 65 66 67 68 69

/**
 * 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
70
 * depending on what “icon theme” is selected
71
 * by the user. The operation of icon themes on Linux and Unix
72
 * follows the [Icon Theme Specification](http://www.freedesktop.org/Standards/icon-theme-spec)
73 74 75
 * 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.
76
 *
77 78
 * Named icons are similar to the deprecated [Stock Items][gtkstock],
 * and the distinction between the two may be a bit confusing.
79
 * A few things to keep in mind:
80 81
 * 
 * - Stock images usually are used in conjunction with
82
 *   [Stock Items][gtkstock], such as %GTK_STOCK_OK or
83 84 85 86 87 88 89 90 91 92 93 94 95 96
 *   %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.
 *
97 98 99 100 101 102
 * 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
103
 * “gtk-stock-go-forward-ltr” and “gtk-stock-go-forward-rtl”.)
104 105 106 107 108
 *
 * 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
109
 * can create new #GtkIconTheme objects, but it’s much more
110 111
 * efficient to use the standard icon theme for the #GdkScreen
 * so that the icon information is shared with other people
112
 * looking up icons.
113
 * |[<!-- language="C" -->
114 115 116 117 118 119
 * GError *error = NULL;
 * GtkIconTheme *icon_theme;
 * GdkPixbuf *pixbuf;
 *
 * icon_theme = gtk_icon_theme_get_default ();
 * pixbuf = gtk_icon_theme_load_icon (icon_theme,
120 121 122
 *                                    "my-icon-name", // icon name
 *                                    48, // icon size
 *                                    0,  // flags
123 124 125
 *                                    &error);
 * if (!pixbuf)
 *   {
126
 *     g_warning ("Couldn’t load icon: %s", error->message);
127 128 129 130
 *     g_error_free (error);
 *   }
 * else
 *   {
131
 *     // Use the pixbuf
132 133
 *     g_object_unref (pixbuf);
 *   }
134
 * ]|
135 136
 */

137
#define FALLBACK_ICON_THEME "hicolor"
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

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,
153
  ICON_SUFFIX_PNG = 1 << 2,
154 155
  HAS_ICON_FILE = 1 << 3,
  ICON_SUFFIX_SYMBOLIC_PNG = 1 << 4
156 157
} IconSuffix;

Alexander Larsson's avatar
Alexander Larsson committed
158 159 160 161 162 163
#define INFO_CACHE_LRU_SIZE 32
#if 0
#define DEBUG_CACHE(args) g_print args
#else
#define DEBUG_CACHE(args)
#endif
164 165 166

struct _GtkIconThemePrivate
{
Alexander Larsson's avatar
Alexander Larsson committed
167 168 169
  GHashTable *info_cache;
  GList *info_cache_lru;

170 171 172
  gchar *current_theme;
  gchar **search_path;
  gint search_path_len;
173
  GList *resource_paths;
174

175
  guint custom_theme        : 1;
176 177
  guint is_screen_singleton : 1;
  guint pixbuf_supports_svg : 1;
178
  guint themes_valid        : 1;
179
  guint loading_themes      : 1;
180 181 182 183 184 185

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

187
  /* GdkScreen for the icon theme (may be NULL) */
188
  GdkScreen *screen;
189

190
  /* time when we last stat:ed for theme changes */
191
  glong last_stat_time;
192
  GList *dir_mtimes;
193

194
  gulong theme_changed_idle;
195 196
};

Alexander Larsson's avatar
Alexander Larsson committed
197 198 199
typedef struct {
  gchar **icon_names;
  gint size;
200
  gint scale;
Alexander Larsson's avatar
Alexander Larsson committed
201 202 203
  GtkIconLookupFlags flags;
} IconInfoKey;

204 205 206 207 208 209 210 211 212 213 214 215
typedef struct _SymbolicPixbufCache SymbolicPixbufCache;

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

216 217 218 219 220
struct _GtkIconInfoClass
{
  GObjectClass parent_class;
};

221 222
struct _GtkIconInfo
{
223 224
  GObject parent_instance;

225 226
  /* Information about the source
   */
Alexander Larsson's avatar
Alexander Larsson committed
227 228 229
  IconInfoKey key;
  GtkIconTheme *in_cache;

230
  gchar *filename;
231
  GFile *icon_file;
232
  GLoadableIcon *loadable;
233
  GSList *emblem_infos;
234

Anders Carlsson's avatar
Anders Carlsson committed
235 236 237
  /* Cache pixbuf (if there is any) */
  GdkPixbuf *cache_pixbuf;

238 239 240 241 242
  /* Information about the directory where
   * the source was found
   */
  IconThemeDirType dir_type;
  gint dir_size;
243
  gint dir_scale;
244 245
  gint min_size;
  gint max_size;
246 247 248 249

  /* Parameters influencing the scaled icon
   */
  gint desired_size;
250
  gint desired_scale;
251
  guint forced_size     : 1;
252
  guint emblems_applied : 1;
253
  guint is_svg          : 1;
254
  guint is_resource     : 1;
255

256 257 258 259
  /* Cached information if we go ahead and try to load
   * the icon.
   */
  GdkPixbuf *pixbuf;
Alexander Larsson's avatar
Alexander Larsson committed
260
  GdkPixbuf *proxy_pixbuf;
261
  GError *load_error;
262
  gdouble unscaled_scale;
263
  gdouble scale;
264

265
  SymbolicPixbufCache *symbolic_pixbuf_cache;
266

267 268
  gint symbolic_width;
  gint symbolic_height;
269 270 271 272
};

typedef struct
{
Matthias Clasen's avatar
Matthias Clasen committed
273 274 275 276
  gchar *name;
  gchar *display_name;
  gchar *comment;
  gchar *example;
277 278 279 280 281 282 283 284 285 286

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

typedef struct
{
  IconThemeDirType type;
  GQuark context;

Matthias Clasen's avatar
Matthias Clasen committed
287 288 289 290 291
  gint size;
  gint min_size;
  gint max_size;
  gint threshold;
  gint scale;
292
  gboolean is_resource;
293

Matthias Clasen's avatar
Matthias Clasen committed
294 295 296
  gchar *dir;
  gchar *subdir;
  gint subdir_index;
297 298
  
  GtkIconCache *cache;
299 300 301 302 303 304
  
  GHashTable *icons;
} IconThemeDir;

typedef struct
{
Matthias Clasen's avatar
Matthias Clasen committed
305 306
  gchar *svg_filename;
  gchar *no_svg_filename;
307
  gboolean is_resource;
308 309 310 311 312 313 314 315 316 317
} UnthemedIcon;

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

typedef struct 
{
Matthias Clasen's avatar
Matthias Clasen committed
318
  gchar *dir;
319
  time_t mtime;
320
  GtkIconCache *cache;
321
  gboolean exists;
322 323
} IconThemeDirMtime;

Matthias Clasen's avatar
Matthias Clasen committed
324 325 326 327 328 329 330 331 332 333 334 335
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);
336 337
static gboolean     theme_has_icon            (IconTheme        *theme,
                                               const gchar      *icon_name);
Matthias Clasen's avatar
Matthias Clasen committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
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);
361
static gboolean     icon_info_ensure_scale_and_pixbuf (GtkIconInfo* icon_info);
362 363 364 365 366

static guint signal_changed = 0;

static GHashTable *icon_theme_builtin_icons;

Alexander Larsson's avatar
Alexander Larsson committed
367 368 369 370 371 372 373 374 375 376
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;
377 378
  h ^= key->scale * 0x1000010;
  h ^= key->flags * 0x100000100;
Alexander Larsson's avatar
Alexander Larsson committed
379 380 381 382 383

  return h;
}

static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
384 385
icon_info_key_equal (gconstpointer _a,
                     gconstpointer _b)
Alexander Larsson's avatar
Alexander Larsson committed
386 387 388 389 390 391 392 393
{
  const IconInfoKey *a = _a;
  const IconInfoKey *b = _b;
  int i;

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

394 395 396
  if (a->scale != b->scale)
    return FALSE;

Alexander Larsson's avatar
Alexander Larsson committed
397 398 399 400 401 402 403 404
  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
405
        return FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
406 407 408 409 410
    }

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

411
G_DEFINE_TYPE_WITH_PRIVATE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT)
412 413 414 415 416 417

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

489
      g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), icon_theme);
490 491 492 493 494 495 496 497 498 499 500 501
    }

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


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

  if (was_screen_singleton)
    {
535
      g_object_set_data (G_OBJECT (screen), I_("gtk-icon-theme"), NULL);
536 537 538 539 540 541 542 543 544 545 546 547 548 549
      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)
{
550 551 552
#define theme_changed(_old, _new) \
  ((_old && !_new) || (!_old && _new) || \
   (_old && _new && strcmp (_old, _new) != 0))
553 554 555 556 557
  GtkIconThemePrivate *priv = icon_theme->priv;

  if (!priv->custom_theme)
    {
      gchar *theme = NULL;
558
      gboolean changed = FALSE;
559 560

      if (priv->screen)
Matthias Clasen's avatar
Matthias Clasen committed
561 562 563 564
        {
          GtkSettings *settings = gtk_settings_get_for_screen (priv->screen);
          g_object_get (settings, "gtk-icon-theme-name", &theme, NULL);
        }
565

566
      if (theme_changed (priv->current_theme, theme))
Matthias Clasen's avatar
Matthias Clasen committed
567 568 569 570 571
        {
          g_free (priv->current_theme);
          priv->current_theme = theme;
          changed = TRUE;
        }
572
      else
Matthias Clasen's avatar
Matthias Clasen committed
573
        g_free (theme);
574 575

      if (changed)
Matthias Clasen's avatar
Matthias Clasen committed
576
        do_theme_change (icon_theme);
577
    }
578
#undef theme_changed
579 580 581 582 583 584
}

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

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

  update_current_theme (icon_theme);
}

/* Checks whether a loader for SVG files has been registered
 * with GdkPixbuf.
 */
static gboolean
660
pixbuf_supports_svg (void)
661
{
662
  GSList *formats;
663
  GSList *tmp_list;
Matthias Clasen's avatar
Matthias Clasen committed
664
  static gint found_svg = -1;
665

Matthias Clasen's avatar
Matthias Clasen committed
666
  if (found_svg != -1)
667
    return found_svg;
668 669 670

  formats = gdk_pixbuf_get_formats ();

Matthias Clasen's avatar
Matthias Clasen committed
671
  found_svg = FALSE; 
672 673 674 675 676 677
  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
678 679 680 681
        {
          if (strcmp (*mime_type, "image/svg") == 0)
            found_svg = TRUE;
        }
682

683
      g_strfreev (mime_types);
684 685 686
    }

  g_slist_free (formats);
687
  
688 689 690
  return found_svg;
}

Alexander Larsson's avatar
Alexander Larsson committed
691 692 693 694 695 696 697
/* 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
698 699 700 701 702
                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
703 704 705 706 707 708 709

  icon_info->in_cache = NULL;

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

710 711 712 713
static void
gtk_icon_theme_init (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv;
714
  const gchar * const *xdg_data_dirs;
715
  int i, j;
716

717
  priv = gtk_icon_theme_get_instance_private (icon_theme);
718 719
  icon_theme->priv = priv;

Alexander Larsson's avatar
Alexander Larsson committed
720
  priv->info_cache = g_hash_table_new_full (icon_info_key_hash, icon_info_key_equal, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
721
                                            (GDestroyNotify)icon_info_uncached);
Alexander Larsson's avatar
Alexander Larsson committed
722

723
  priv->custom_theme = FALSE;
724 725 726 727

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

728
  priv->search_path_len = 2 * i + 2;
729 730 731 732 733
  
  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);
734
  priv->search_path[i++] = g_build_filename (g_get_home_dir (), ".icons", NULL);
735
  
736 737
  for (j = 0; xdg_data_dirs[j]; j++) 
    priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "icons", NULL);
738

739 740
  for (j = 0; xdg_data_dirs[j]; j++) 
    priv->search_path[i++] = g_build_filename (xdg_data_dirs[j], "pixmaps", NULL);
741

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

744 745 746
  priv->themes_valid = FALSE;
  priv->themes = NULL;
  priv->unthemed_icons = NULL;
747
  
748 749 750 751 752 753
  priv->pixbuf_supports_svg = pixbuf_supports_svg ();
}

static void
free_dir_mtime (IconThemeDirMtime *dir_mtime)
{
754 755 756
  if (dir_mtime->cache)
    _gtk_icon_cache_unref (dir_mtime->cache);

757
  g_free (dir_mtime->dir);
Matthias Clasen's avatar
Matthias Clasen committed
758
  g_slice_free (IconThemeDirMtime, dir_mtime);
759 760
}

761
static gboolean
762
theme_changed_idle (gpointer user_data)
763 764 765 766 767 768 769
{
  GtkIconTheme *icon_theme;
  GtkIconThemePrivate *priv;

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

770 771
  g_signal_emit (icon_theme, signal_changed, 0);

772
  if (priv->screen && priv->is_screen_singleton)
773
    gtk_style_context_reset_widgets (priv->screen);
774

775
  priv->theme_changed_idle = 0;
776 777 778 779

  return FALSE;
}

780 781 782 783 784 785
static void
queue_theme_changed (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;

  if (!priv->theme_changed_idle)
786 787 788 789 790 791
    {
      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");
    }
792 793
}

794 795 796 797
static void
do_theme_change (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
798

Alexander Larsson's avatar
Alexander Larsson committed
799 800
  g_hash_table_remove_all (priv->info_cache);

801 802
  if (!priv->themes_valid)
    return;
803

804
  GTK_NOTE (ICONTHEME, 
Matthias Clasen's avatar
Matthias Clasen committed
805
            g_print ("change to icon theme \"%s\"\n", priv->current_theme));
806
  blow_themes (icon_theme);
807

808 809
  queue_theme_changed (icon_theme);

810 811 812 813 814 815 816 817 818
}

static void
blow_themes (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
  
  if (priv->themes_valid)
    {
819 820
      g_list_free_full (priv->themes, (GDestroyNotify) theme_destroy);
      g_list_free_full (priv->dir_mtimes, (GDestroyNotify) free_dir_mtime);
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
      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
839 840 841
  g_hash_table_destroy (priv->info_cache);
  g_assert (priv->info_cache_lru == NULL);

842
  if (priv->theme_changed_idle)
843
    g_source_remove (priv->theme_changed_idle);
844

845 846 847 848
  unset_screen (icon_theme);

  g_free (priv->current_theme);

849
  for (i = 0; i < priv->search_path_len; i++)
850 851
    g_free (priv->search_path[i]);
  g_free (priv->search_path);
852 853

  g_list_free_full (priv->resource_paths, g_free);
854 855

  blow_themes (icon_theme);
856

Matthias Clasen's avatar
Matthias Clasen committed
857
  G_OBJECT_CLASS (gtk_icon_theme_parent_class)->finalize (object);  
858 859 860 861 862
}

/**
 * gtk_icon_theme_set_search_path:
 * @icon_theme: a #GtkIconTheme
863 864
 * @path: (array length=n_elements) (element-type filename): array of
 *     directories that are searched for icon themes
865 866 867 868 869
 * @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
870 871 872
 * 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.)
873
 *
874
 * In addition if an icon found isn’t found either in the current
875 876 877 878
 * 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
879
 * into the fallback icon theme, which is called hicolor,
880
 * rather than directly on the icon path.)
Matthias Clasen's avatar
Matthias Clasen committed
881 882
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
883
 */
884 885
void
gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme,
Matthias Clasen's avatar
Matthias Clasen committed
886 887
                                const gchar  *path[],
                                gint          n_elements)
888 889 890 891 892 893 894 895 896 897 898 899 900 901
{
  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;
902

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

  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
940
        (*path)[i] = g_strdup (priv->search_path[i]);
941 942 943 944 945 946 947
      (*path)[i] = NULL;
    }
}

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

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

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

999 1000
  for (i = priv->search_path_len - 1; i > 0; i--)
    priv->search_path[i] = priv->search_path[i - 1];
1001 1002 1003 1004 1005 1006
  
  priv->search_path[0] = g_strdup (path);

  do_theme_change (icon_theme);
}

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
/**
 * 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
 */
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
void
gtk_icon_theme_add_resource_path (GtkIconTheme *icon_theme,
                                  const gchar  *path)
{
  GtkIconThemePrivate *priv = icon_theme->priv;

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

  priv->resource_paths = g_list_append (priv->resource_paths, g_strdup (path));

  do_theme_change (icon_theme);
}

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

Matthias Clasen's avatar
Matthias Clasen committed
1073 1074
          do_theme_change (icon_theme);
        }
1075 1076 1077
    }
  else
    {
1078
      if (priv->custom_theme)
Matthias Clasen's avatar
Matthias Clasen committed
1079 1080 1081 1082
        {
          priv->custom_theme = FALSE;
          update_current_theme (icon_theme);
        }
1083 1084 1085
    }
}

1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
static const gchar builtin_hicolor_index[] =
"[Icon Theme]\n"
"Name=Hicolor\n"
"Hidden=True\n"
"Directories=16x16/actions,22x22/actions,24x24/actions,32x32/actions\n"
"[16x16/actions]\n"
"Size=16\n"
"Type=Threshold\n"
"[22x22/actions]\n"
"Size=22\n"
"Type=Threshold\n"
"[24x24/actions]\n"
"Size=24\n"
"Type=Threshold\n"
"[32x32/actions]\n"
"Size=32\n"
"Type=Threshold\n";

1104
static void
Matthias Clasen's avatar
Matthias Clasen committed
1105 1106
insert_theme (GtkIconTheme *icon_theme,
              const gchar  *theme_name)
1107
{
Matthias Clasen's avatar
Matthias Clasen committed
1108
  gint i;
1109
  GList *l;
Matthias Clasen's avatar
Matthias Clasen committed
1110 1111 1112
  gchar **dirs;
  gchar **scaled_dirs;
  gchar **themes;
1113
  GtkIconThemePrivate *priv;
1114
  IconTheme *theme = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1115
  gchar *path;
1116 1117
  GKeyFile *theme_file;
  GError *error = NULL;
1118
  IconThemeDirMtime *dir_mtime;
1119
  GStatBuf stat_buf;
1120 1121
  
  priv = icon_theme->priv;
1122

1123 1124 1125 1126
  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
1127
        return;
1128 1129 1130 1131 1132
    }
  
  for (i = 0; i < priv->search_path_len; i++)
    {
      path = g_build_filename (priv->search_path[i],
Matthias Clasen's avatar
Matthias Clasen committed
1133 1134
                               theme_name,
                               NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1135
      dir_mtime = g_slice_new (IconThemeDirMtime);
1136
      dir_mtime->cache = NULL;
1137
      dir_mtime->dir = path;
1138
      if (g_stat (path, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode)) {
Matthias Clasen's avatar
Matthias Clasen committed
1139
        dir_mtime->mtime = stat_buf.st_mtime;
1140 1141
        dir_mtime->exists = TRUE;
      } else {
Matthias Clasen's avatar
Matthias Clasen committed
1142
        dir_mtime->mtime = 0;
1143 1144
        dir_mtime->exists = FALSE;
      }
1145 1146 1147

      priv->dir_mtimes = g_list_prepend (priv->dir_mtimes, dir_mtime);
    }
1148
  priv->dir_mtimes = g_list_reverse (priv->dir_mtimes);
1149 1150

  theme_file = NULL;
1151
  for (i = 0; i < priv->search_path_len && !theme_file; i++)
1152 1153
    {
      path = g_build_filename (priv->search_path[i],
Matthias Clasen's avatar
Matthias Clasen committed
1154 1155 1156
                               theme_name,
                               "index.theme",
                               NULL);
1157
      if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) 
Matthias Clasen's avatar
Matthias Clasen committed
1158 1159 1160
        {
          theme_file = g_key_file_new ();
          g_key_file_set_list_separator (theme_file, ',');
1161
          if (!g_key_file_load_from_file (theme_file, path, 0, &error))
Matthias Clasen's avatar
Matthias Clasen committed
1162 1163 1164 1165 1166 1167 1168
            {
              g_key_file_free (theme_file);
              theme_file = NULL;
              g_error_free (error);
              error = NULL;
            }
        }
1169 1170 1171
      g_free (path);
    }

1172
  if (theme_file || strcmp (theme_name, FALLBACK_ICON_THEME) == 0)
1173 1174 1175 1176
    {
      theme = g_new0 (IconTheme, 1);
      theme->name = g_strdup (theme_name);
      priv->themes = g_list_prepend (priv->themes, theme);
1177 1178 1179 1180 1181 1182
      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);
        }
1183 1184
    }

1185 1186
  if (theme_file == NULL)
    return;
1187

1188 1189 1190
  theme->display_name = 
    g_key_file_get_locale_string (theme_file, "Icon Theme", "Name", NULL, NULL);
  if (!theme->display_name)
1191
    g_warning ("Theme file for %s has no name\n", theme_name);
1192

1193 1194
  dirs = g_key_file_get_string_list (theme_file, "Icon Theme", "Directories", NULL, NULL);
  if (!dirs)
Matthias Clasen's avatar
Matthias Clasen committed
1195 1196
    {
      g_warning ("Theme file for %s has no directories\n", theme_name);
1197 1198
      priv->themes = g_list_remove (priv->themes, theme);
      g_free (theme->name);
Matthias Clasen's avatar
Matthias Clasen committed
1199 1200 1201 1202 1203
      g_free (theme->display_name);
      g_free (theme);
      g_key_file_free (theme_file);
      return;
    }
1204 1205 1206

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

1207 1208
  theme->comment = 
    g_key_file_get_locale_string (theme_file, 
Matthias Clasen's avatar
Matthias Clasen committed
1209 1210
                                  "Icon Theme", "Comment",
                                  NULL, NULL);
1211 1212
  theme->example = 
    g_key_file_get_string (theme_file, 
Matthias Clasen's avatar
Matthias Clasen committed
1213 1214
                           "Icon Theme", "Example",
                           NULL);
1215 1216

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

1220 1221 1222 1223 1224
  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
1225
  g_strfreev (dirs);
1226
  g_strfreev (scaled_dirs);
Matthias Clasen's avatar
Matthias Clasen committed
1227

1228 1229
  theme->dirs = g_list_reverse (theme->dirs);

1230
  themes = g_key_file_get_string_list (theme_file,
Matthias Clasen's avatar
Matthias Clasen committed
1231 1232 1233 1234
                                       "Icon Theme",
                                       "Inherits",
                                       NULL,
                                       NULL);
1235
  if (themes)
1236 1237
    {
      for (i = 0; themes[i] != NULL; i++)
Matthias Clasen's avatar
Matthias Clasen committed
1238
        insert_theme (icon_theme, themes[i]);
1239 1240 1241 1242
      
      g_strfreev (themes);
    }

1243
  g_key_file_free (theme_file);
1244 1245 1246 1247 1248
}

static void
free_unthemed_icon (UnthemedIcon *unthemed_icon)
{
1249 1250
  g_free (unthemed_icon->svg_filename);
  g_free (unthemed_icon->no_svg_filename);
1251
  g_slice_free (UnthemedIcon, unthemed_icon);
1252 1253
}

Matthias Clasen's avatar
Matthias Clasen committed
1254 1255
static gchar *
strip_suffix (const gchar *filename)
1256
{
Matthias Clasen's avatar
Matthias Clasen committed
1257
  const gchar *dot;
1258

1259 1260 1261
  if (g_str_has_suffix (filename, ".symbolic.png"))
    return g_strndup (filename, strlen(filename)-13);

1262 1263 1264 1265 1266 1267 1268 1269
  dot = strrchr (filename, '.');

  if (dot == NULL)
    return g_strdup (filename);

  return g_strndup (filename, dot - filename);
}

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
static void
add_unthemed_icon (GtkIconTheme *icon_theme,
                   const gchar  *dir,
                   const gchar  *file,
                   gboolean      is_resource)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
  IconSuffix new_suffix, old_suffix;
  gchar *abs_file;
  gchar *base_name;
  UnthemedIcon *unthemed_icon;

  new_suffix = suffix_from_name (file);

  if (new_suffix == ICON_SUFFIX_NONE)
    return;

  abs_file = g_build_filename (dir, file, NULL);
  base_name = strip_suffix (file);

  unthemed_icon = g_hash_table_lookup (priv->unthemed_icons, base_name);

  if (unthemed_icon)
    {
      if (new_suffix == ICON_SUFFIX_SVG)
        {
          if (unthemed_icon->svg_filename)
            g_free (abs_file);
          else
            unthemed_icon->svg_filename = abs_file;
        }
      else
        {
          if (unthemed_icon->no_svg_filename)
            {
              old_suffix = suffix_from_name (unthemed_icon->no_svg_filename);
              if (new_suffix > old_suffix)
                {
                  g_free (unthemed_icon->no_svg_filename);
                  unthemed_icon->no_svg_filename = abs_file;
                }
              else
                g_free (abs_file);
            }
          else
            unthemed_icon->no_svg_filename = abs_file;
        }

      g_free (base_name);
    }
  else
    {
      unthemed_icon = g_slice_new0 (UnthemedIcon);

      unthemed_icon->is_resource = is_resource;

      if (new_suffix == ICON_SUFFIX_SVG)
        unthemed_icon->svg_filename = abs_file;
      else
        unthemed_icon->no_svg_filename = abs_file;

      /* takes ownership of base_name */
      g_hash_table_replace (priv->unthemed_icons, base_name, unthemed_icon);
    }
}

1336 1337 1338 1339 1340
static void
load_themes (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv;
  GDir *gdir;
Matthias Clasen's avatar
Matthias Clasen committed
1341 1342 1343
  gint base;
  gchar *dir;
  const gchar *file;
1344
  GTimeVal tv;
1345
  IconThemeDirMtime *dir_mtime;
1346
  GStatBuf stat_buf;
1347
  GList *d;
1348 1349 1350
  
  priv = icon_theme->priv;

1351 1352
  if (priv->current_theme)
    insert_theme (icon_theme, priv->current_theme);
1353

1354 1355 1356 1357
  /* Always look in the Adwaita, gnome and hicolor icon themes.
   * Looking in hicolor is mandated by the spec, looking in Adwaita
   * and gnome is a pragmatic solution to prevent missing icons in
   * GTK+ applications when run under, e.g. KDE.
1358
   */
1359
  insert_theme (icon_theme, DEFAULT_ICON_THEME);
1360
  insert_theme (icon_theme, "gnome");
1361
  insert_theme (icon_theme, FALLBACK_ICON_THEME);
1362
  priv->themes = g_list_reverse (priv->themes);
1363 1364


1365
  priv->unthemed_icons = g_hash_table_new_full (g_str_hash, g_str_equal,
Matthias Clasen's avatar
Matthias Clasen committed
1366
                                                g_free, (GDestroyNotify)free_unthemed_icon);
1367 1368 1369 1370

  for (base = 0; base < icon_theme->priv->search_path_len; base++)
    {
      dir = icon_theme->priv->search_path[base];
1371

Matthias Clasen's avatar
Matthias Clasen committed
1372
      dir_mtime = g_slice_new (IconThemeDirMtime);
1373
      priv->dir_mtimes = g_list_append (priv->dir_mtimes, dir_mtime);
1374
      
1375 1376
      dir_mtime->dir = g_strdup (dir);
      dir_mtime->mtime = 0;
1377
      dir_mtime->exists = FALSE;
1378 1379 1380
      dir_mtime->cache = NULL;

      if (g_stat (dir, &stat_buf) != 0 || !S_ISDIR (stat_buf.st_mode))
Matthias Clasen's avatar
Matthias Clasen committed
1381
        continue;
1382
      dir_mtime->mtime = stat_buf.st_mtime;
1383
      dir_mtime->exists = TRUE;
1384 1385

      dir_mtime->cache = _gtk_icon_cache_new_for_path (dir);
1386
      if (dir_mtime->cache != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1387
        continue;
1388

1389 1390
      gdir = g_dir_open (dir, 0, NULL);
      if (gdir == NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1391
        continue;
1392

1393
      while ((file = g_dir_read_name (gdir)))
1394
        add_unthemed_icon (icon_theme, dir, file, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
1395

1396 1397
      g_dir_close (gdir);
    }
Matthias Clasen's avatar
Matthias Clasen committed
1398

1399 1400 1401 1402
  for (d = priv->resource_paths; d; d = d->next)
    {
      gchar **children;
      gint i;
Matthias Clasen's avatar
Matthias Clasen committed
1403

1404 1405 1406 1407
      dir = d->data;
      children = g_resources_enumerate_children (dir, 0, NULL);
      if (!children)
        continue;
Matthias Clasen's avatar
Matthias Clasen committed
1408

1409 1410
      for (i = 0; children[i]; i++)
        add_unthemed_icon (icon_theme, dir, children[i], TRUE);
Matthias Clasen's avatar
Matthias Clasen committed
1411

1412
      g_strfreev (children);
1413 1414 1415 1416
    }

  priv->themes_valid = TRUE;
  
Matthias Clasen's avatar
Matthias Clasen committed
1417
  g_get_current_time (&tv);
1418
  priv->last_stat_time = tv.tv_sec;
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429

  GTK_NOTE (ICONTHEME, {
    GList *l;
    g_print ("Current icon themes ");
    for (l = icon_theme->priv->themes; l; l = l->next)
      {
        IconTheme *theme = l->data;
        g_print ("%s ", theme->name);
      }
    g_print ("\n");
  });
1430 1431 1432 1433 1434 1435
}

static void
ensure_valid_themes (GtkIconTheme *icon_theme)
{
  GtkIconThemePrivate *priv = icon_theme->priv;
1436
  GTimeVal tv;
1437
  gboolean was_valid = priv->themes_valid;
1438

1439 1440 1441 1442
  if (priv->loading_themes)
    return;
  priv->loading_themes = TRUE;

1443 1444
  if (priv->themes_valid)
    {
Matthias Clasen's avatar
Matthias Clasen committed
1445
      g_get_current_time (&tv);
1446

1447
      if (ABS (tv.tv_sec - priv->last_stat_time) > 5 &&
Matthias Clasen's avatar
Matthias Clasen committed
1448
          rescan_themes (icon_theme))
1449 1450 1451 1452
        {
          g_hash_table_remove_all (priv->info_cache);
          blow_themes (icon_theme);
        }
1453 1454 1455
    }
  
  if (!priv->themes_valid)
1456 1457 1458
    {
      load_themes (icon_theme);

1459
      if (was_valid)
1460
        queue_theme_changed (icon_theme);
1461
    }
1462 1463

  priv->loading_themes = FALSE;