/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
 * Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-icon-factory.h"
#include "thunar/thunar-preferences.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-util.h"



/* the timeout until the sweeper is run (in seconds) */
#define THUNAR_ICON_FACTORY_SWEEP_TIMEOUT (30)



/* Property identifiers */
enum
{
  PROP_0,
  PROP_ICON_THEME,
  PROP_THUMBNAIL_MODE,
  PROP_THUMBNAIL_DRAW_FRAMES,
  PROP_THUMBNAIL_SIZE,
  PROP_THUMBNAIL_MAX_FILE_SIZE,
};



typedef struct _ThunarIconKey ThunarIconKey;



static void
thunar_icon_factory_dispose (GObject *object);
static void
thunar_icon_factory_finalize (GObject *object);
static void
thunar_icon_factory_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec);
static void
thunar_icon_factory_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec);
static gboolean
thunar_icon_factory_changed (GSignalInvocationHint *ihint,
                             guint                  n_param_values,
                             const GValue          *param_values,
                             gpointer               user_data);
static gboolean
thunar_icon_factory_sweep_timer (gpointer user_data);
static void
thunar_icon_factory_sweep_timer_destroy (gpointer user_data);
static GdkPixbuf *
thunar_icon_factory_load_from_file (ThunarIconFactory *factory,
                                    const gchar       *path,
                                    gint               size,
                                    gint               scale_factor);
static GdkPixbuf *
thunar_icon_factory_lookup_icon (ThunarIconFactory *factory,
                                 const gchar       *name,
                                 gint               size,
                                 gint               scale_factor,
                                 gboolean           wants_default,
                                 gboolean           symbolic,
                                 GtkStyleContext   *context);
static guint
thunar_icon_key_hash (gconstpointer data);
static gboolean
thunar_icon_key_equal (gconstpointer a,
                       gconstpointer b);
static void
thunar_icon_key_free (gpointer data);
static GdkPixbuf *
thunar_icon_factory_load_fallback (ThunarIconFactory *factory,
                                   gint               size,
                                   gint               scale_factor,
                                   gboolean           symbolic);



struct _ThunarIconFactoryClass
{
  GObjectClass __parent__;
};

struct _ThunarIconFactory
{
  GObject __parent__;

  ThunarPreferences *preferences;

  GHashTable *icon_cache;

  GtkIconTheme *icon_theme;

  ThunarThumbnailMode thumbnail_mode;

  gboolean thumbnail_draw_frames;

  ThunarThumbnailSize thumbnail_size;

  /* maximum file size (in bytes) allowed to be thumbnailed */
  guint64 thumbnail_max_file_size;

  guint sweep_timer_id;

  gulong changed_hook_id;

  /* stamp that gets bumped when the theme changes */
  guint theme_stamp;
};

struct _ThunarIconKey
{
  gchar   *name;
  gint     size;
  gint     scale_factor;
  gboolean symbolic;
  guint    color_hash;
};

typedef struct
{
  ThunarFileIconState  icon_state;
  ThunarFileThumbState thumb_state;
  gint                 icon_size;
  guint                stamp;
  gboolean             thumbnail_draw_frames;
  gboolean             symbolic;
  GdkPixbuf           *icon;
} ThunarIconStore;



static GQuark thunar_icon_factory_quark = 0;
static GQuark thunar_icon_factory_store_quark = 0;



G_DEFINE_TYPE (ThunarIconFactory, thunar_icon_factory, G_TYPE_OBJECT)



static void
thunar_icon_factory_class_init (ThunarIconFactoryClass *klass)
{
  GObjectClass *gobject_class;

  thunar_icon_factory_store_quark = g_quark_from_static_string ("thunar-icon-factory-store");

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->dispose = thunar_icon_factory_dispose;
  gobject_class->finalize = thunar_icon_factory_finalize;
  gobject_class->get_property = thunar_icon_factory_get_property;
  gobject_class->set_property = thunar_icon_factory_set_property;

  /**
   * ThunarIconFactory:icon-theme:
   *
   * The #GtkIconTheme on which the given #ThunarIconFactory instance operates
   * on.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ICON_THEME,
                                   g_param_spec_object ("icon-theme",
                                                        "icon-theme",
                                                        "icon-theme",
                                                        GTK_TYPE_ICON_THEME,
                                                        EXO_PARAM_READABLE));

  /**
   * ThunarIconFactory:thumbnail-mode:
   *
   * Whether this #ThunarIconFactory will try to generate and load thumbnails
   * when loading icons for #ThunarFile<!---->s.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_THUMBNAIL_MODE,
                                   g_param_spec_enum ("thumbnail-mode",
                                                      "thumbnail-mode",
                                                      "thumbnail-mode",
                                                      THUNAR_TYPE_THUMBNAIL_MODE,
                                                      THUNAR_THUMBNAIL_MODE_ONLY_LOCAL,
                                                      EXO_PARAM_READWRITE));

  /**
   * ThunarIconFactory:thumbnail-draw-frames:
   *
   * Whether to draw black frames around thumbnails.
   * This looks neat, but will delay the first draw a bit.
   * May have an impact on older systems, on folders with many pictures.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_THUMBNAIL_DRAW_FRAMES,
                                   g_param_spec_boolean ("thumbnail-draw-frames",
                                                         "thumbnail-draw-frames",
                                                         "thumbnail-draw-frames",
                                                         FALSE,
                                                         EXO_PARAM_READWRITE));

  /**
   * ThunarIconFactory:thumbnail-size:
   *
   * Size of the thumbnails to load
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_THUMBNAIL_SIZE,
                                   g_param_spec_enum ("thumbnail-size",
                                                      "thumbnail-size",
                                                      "thumbnail-size",
                                                      THUNAR_TYPE_THUMBNAIL_SIZE,
                                                      THUNAR_THUMBNAIL_SIZE_NORMAL,
                                                      EXO_PARAM_READWRITE));

  /**
   * ThunarIconFactory:thumbnail-max-file-size:
   *
   * Maximum file size (in bytes) allowed to be thumbnailed
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_THUMBNAIL_MAX_FILE_SIZE,
                                   g_param_spec_uint64 ("thumbnail-max-file-size",
                                                        "thumbnail-max-file-size",
                                                        "thumbnail-max-file-size",
                                                        0, G_MAXUINT64, 0,
                                                        EXO_PARAM_READWRITE));
}



static void
thunar_icon_factory_init (ThunarIconFactory *factory)
{
  factory->thumbnail_mode = THUNAR_THUMBNAIL_MODE_ONLY_LOCAL;
  factory->thumbnail_size = THUNAR_THUMBNAIL_SIZE_NORMAL;

  /* connect emission hook for the "changed" signal on the GtkIconTheme class. We use the emission
   * hook way here, because that way we can make sure that the icon cache is definetly cleared
   * before any other part of the application gets notified about the icon theme change.
   */
  factory->changed_hook_id = g_signal_add_emission_hook (g_signal_lookup ("changed", GTK_TYPE_ICON_THEME),
                                                         0, thunar_icon_factory_changed, factory, NULL);

  /* allocate the hash table for the icon cache */
  factory->icon_cache = g_hash_table_new_full (thunar_icon_key_hash, thunar_icon_key_equal,
                                               thunar_icon_key_free, g_object_unref);
}



static void
thunar_icon_factory_dispose (GObject *object)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);

  _thunar_return_if_fail (THUNAR_IS_ICON_FACTORY (factory));

  if (G_UNLIKELY (factory->sweep_timer_id != 0))
    g_source_remove (factory->sweep_timer_id);

  (*G_OBJECT_CLASS (thunar_icon_factory_parent_class)->dispose) (object);
}



static void
thunar_icon_factory_finalize (GObject *object)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);

  _thunar_return_if_fail (THUNAR_IS_ICON_FACTORY (factory));

  /* clear the icon cache hash table */
  g_hash_table_destroy (factory->icon_cache);

  /* remove the "changed" emission hook from the GtkIconTheme class */
  g_signal_remove_emission_hook (g_signal_lookup ("changed", GTK_TYPE_ICON_THEME), factory->changed_hook_id);

  /* disconnect from the associated icon theme (if any) */
  if (G_LIKELY (factory->icon_theme != NULL))
    {
      g_object_set_qdata (G_OBJECT (factory->icon_theme), thunar_icon_factory_quark, NULL);
      g_object_unref (G_OBJECT (factory->icon_theme));
    }

  /* disconnect from the preferences */
  g_object_unref (G_OBJECT (factory->preferences));

  (*G_OBJECT_CLASS (thunar_icon_factory_parent_class)->finalize) (object);
}



static void
thunar_icon_factory_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);

  switch (prop_id)
    {
    case PROP_ICON_THEME:
      g_value_set_object (value, factory->icon_theme);
      break;

    case PROP_THUMBNAIL_MODE:
      g_value_set_enum (value, factory->thumbnail_mode);
      break;

    case PROP_THUMBNAIL_DRAW_FRAMES:
      g_value_set_boolean (value, factory->thumbnail_draw_frames);
      break;

    case PROP_THUMBNAIL_SIZE:
      g_value_set_enum (value, factory->thumbnail_size);
      break;

    case PROP_THUMBNAIL_MAX_FILE_SIZE:
      g_value_set_uint64 (value, factory->thumbnail_max_file_size);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static void
thunar_icon_factory_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);

  switch (prop_id)
    {
    case PROP_THUMBNAIL_MODE:
      factory->thumbnail_mode = g_value_get_enum (value);
      break;

    case PROP_THUMBNAIL_DRAW_FRAMES:
      factory->thumbnail_draw_frames = g_value_get_boolean (value);
      break;

    case PROP_THUMBNAIL_SIZE:
      factory->thumbnail_size = g_value_get_enum (value);
      break;

    case PROP_THUMBNAIL_MAX_FILE_SIZE:
      factory->thumbnail_max_file_size = g_value_get_uint64 (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static gboolean
thunar_icon_factory_changed (GSignalInvocationHint *ihint,
                             guint                  n_param_values,
                             const GValue          *param_values,
                             gpointer               user_data)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (user_data);

  /* drop all items from the icon cache */
  g_hash_table_remove_all (factory->icon_cache);

  /* bump the stamp so all file icons are reloaded */
  factory->theme_stamp++;

  /* keep the emission hook alive */
  return TRUE;
}



static gboolean
thunar_icon_check_sweep (ThunarIconKey *key,
                         GdkPixbuf     *pixbuf)
{
  return (G_OBJECT (pixbuf)->ref_count == 1);
}



static gboolean
thunar_icon_factory_sweep_timer (gpointer user_data)
{
  ThunarIconFactory *factory = THUNAR_ICON_FACTORY (user_data);

  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), FALSE);

  /* ditch all icons whose ref_count is 1 */
  g_hash_table_foreach_remove (factory->icon_cache,
                               (GHRFunc) (void (*) (void)) thunar_icon_check_sweep,
                               factory);

  return FALSE;
}



static void
thunar_icon_factory_sweep_timer_destroy (gpointer user_data)
{
  THUNAR_ICON_FACTORY (user_data)->sweep_timer_id = 0;
}



static inline gboolean
thumbnail_needs_frame (const GdkPixbuf *thumbnail,
                       gint             width,
                       gint             height,
                       gint             size)
{
  const guchar *pixels;
  gint          rowstride;
  gint          n;
  guchar        alpha_threshold = 255u; /* An alpha value of 255 means full opacity, 0 is fully transparent. */

  /* don't add frames to small thumbnails */
  if (size < THUNAR_ICON_SIZE_64)
    return FALSE;

  /* always add a frame to thumbnails w/o alpha channel */
  if (G_LIKELY (!gdk_pixbuf_get_has_alpha (thumbnail)))
    return TRUE;

  /* get a pointer to the thumbnail data */
  pixels = gdk_pixbuf_get_pixels (thumbnail);

  /* Data is stored in 4 channels (red, green, blue, alpha). We are only interested in channel 4 (alpha) */

  /* check if we have a transparent pixel on the first row */
  for (n = width * 4; n > 0; n -= 4)
    if (pixels[n - 1] < alpha_threshold)
      return FALSE;

  /* determine the rowstride */
  rowstride = gdk_pixbuf_get_rowstride (thumbnail);

  /* skip the first row */
  pixels += rowstride;

  /* check if we have a transparent pixel in the first or last column */
  for (n = height - 2; n > 0; --n, pixels += rowstride)
    if (pixels[3] < alpha_threshold || pixels[width * 4 - 1] < alpha_threshold)
      return FALSE;

  /* check if we have a transparent pixel on the last row */
  for (n = width * 4; n > 0; n -= 4)
    if (pixels[n - 1] < alpha_threshold)
      return FALSE;

  return TRUE;
}



static GdkPixbuf *
thunar_icon_factory_get_thumbnail_frame (void)
{
  GInputStream     *stream;
  static GdkPixbuf *frame = NULL;

  if (G_LIKELY (frame != NULL))
    return frame;

  stream = g_resources_open_stream ("/org/xfce/thunar/thumbnail-frame.png", 0, NULL);
  if (G_UNLIKELY (stream != NULL))
    {
      frame = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
      g_object_unref (stream);
    }

  return frame;
}



static GdkPixbuf *
thunar_icon_factory_load_from_file (ThunarIconFactory *factory,
                                    const gchar       *path,
                                    gint               size,
                                    gint               scale_factor)
{
  GdkPixbuf *pixbuf;
  GdkPixbuf *frame;
  GdkPixbuf *tmp;
  gboolean   needs_frame;
  gint       max_width;
  gint       max_height;
  gint       width;
  gint       height;
  gint       scaled_size = size * scale_factor;

  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);

  /* try to load the image from the file */
  pixbuf = gdk_pixbuf_new_from_file (path, NULL);
  if (G_LIKELY (pixbuf != NULL))
    {
      /* determine the dimensions of the pixbuf */
      width = gdk_pixbuf_get_width (pixbuf);
      height = gdk_pixbuf_get_height (pixbuf);

      needs_frame = FALSE;
      if (factory->thumbnail_draw_frames)
        {
          /* check if we want to add a frame to the image */
          needs_frame = (strstr (path, G_DIR_SEPARATOR_S ".cache/thumbnails" G_DIR_SEPARATOR_S) != NULL)
                        && (size >= 32) && thumbnail_needs_frame (pixbuf, width, height, size);
        }

      /* be sure to make framed thumbnails fit into the size */
      if (G_LIKELY (needs_frame))
        {
          max_width = scaled_size - (3 + 6);
          max_height = scaled_size - (3 + 6);
        }
      else
        {
          max_width = scaled_size;
          max_height = scaled_size;
        }

      /* scale down the icon (if required) */
      if (G_LIKELY (width > max_width || height > max_height))
        {
          /* scale down to the required size */
          tmp = exo_gdk_pixbuf_scale_down (pixbuf, TRUE, MAX (1, max_height), MAX (1, max_height));
          g_object_unref (G_OBJECT (pixbuf));
          pixbuf = tmp;
        }

      /* add a frame around thumbnail (large) images */
      if (G_LIKELY (needs_frame))
        {
          /* add a frame to the thumbnail */
          frame = thunar_icon_factory_get_thumbnail_frame ();
          tmp = exo_gdk_pixbuf_frame (pixbuf, frame, 4, 3, 5, 6);
          g_object_unref (G_OBJECT (pixbuf));
          pixbuf = tmp;
        }
    }

  return pixbuf;
}



static GdkPixbuf *
thunar_icon_factory_lookup_icon (ThunarIconFactory *factory,
                                 const gchar       *name,
                                 gint               size,
                                 gint               scale_factor,
                                 gboolean           wants_default,
                                 gboolean           symbolic,
                                 GtkStyleContext   *context)
{
  ThunarIconKey      lookup_key;
  ThunarIconKey     *key;
  GtkIconInfo       *icon_info;
  GtkIconLookupFlags lookup_flags;
  GdkRGBA            color;
  guint              color_hash = 0;
  GdkPixbuf         *pixbuf = NULL;

  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
  _thunar_return_val_if_fail (name != NULL && *name != '\0', NULL);
  _thunar_return_val_if_fail (size > 0, NULL);

  /* check if a symbolic icon that matches the theme is requested */
  if (symbolic && context != NULL)
    {
      /* generate a hash value for the foreground color */
      gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color);
      color_hash = gdk_rgba_hash (&color);
    }

  /* prepare the lookup key */
  lookup_key.name = (gchar *) name;
  lookup_key.size = size;
  lookup_key.scale_factor = scale_factor;
  lookup_key.symbolic = symbolic;
  lookup_key.color_hash = color_hash;

  /* check if we already have a cached version of the icon */
  if (!g_hash_table_lookup_extended (factory->icon_cache, &lookup_key, NULL, (gpointer) &pixbuf))
    {
      /* check if we have to load a file instead of a themed icon */
      if (G_UNLIKELY (g_path_is_absolute (name)))
        {
          /* load the file directly */
          pixbuf = thunar_icon_factory_load_from_file (factory, name, size, scale_factor);
        }
      else
        {
          lookup_flags = GTK_ICON_LOOKUP_FORCE_SIZE;
          if (symbolic)
            lookup_flags |= GTK_ICON_LOOKUP_FORCE_SYMBOLIC;

          /* check if the icon theme contains an icon of that name */
          icon_info = gtk_icon_theme_lookup_icon_for_scale (factory->icon_theme, name, size,
                                                            scale_factor, lookup_flags);
          if (G_LIKELY (icon_info != NULL))
            {
              if (symbolic && context != NULL)
                {
                  /* try to load symbolic icon that matches the theme */
                  pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);
                }
              else
                {
                  /* try to load the pixbuf from the icon info */
                  pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
                }

              /* cleanup */
              g_object_unref (icon_info);
            }
        }

      /* use fallback icon if no pixbuf could be loaded */
      if (G_UNLIKELY (pixbuf == NULL))
        {
          /* check if we are allowed to return the fallback icon */
          if (!wants_default)
            return NULL;
          else
            return thunar_icon_factory_load_fallback (factory, size, scale_factor, symbolic);
        }

      /* generate a key for the new cached icon */
      key = g_slice_new (ThunarIconKey);
      key->name = g_strdup (name);
      key->size = size;
      key->scale_factor = scale_factor;
      key->symbolic = symbolic;
      key->color_hash = color_hash;

      /* insert the new icon into the cache */
      g_hash_table_insert (factory->icon_cache, key, pixbuf);
    }

  /* schedule the sweeper */
  if (G_UNLIKELY (factory->sweep_timer_id == 0))
    {
      factory->sweep_timer_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, THUNAR_ICON_FACTORY_SWEEP_TIMEOUT,
                                                            thunar_icon_factory_sweep_timer, factory,
                                                            thunar_icon_factory_sweep_timer_destroy);
    }

  return GDK_PIXBUF (g_object_ref (G_OBJECT (pixbuf)));
}



static guint
thunar_icon_key_hash (gconstpointer data)
{
  const ThunarIconKey *key = data;
  const gchar         *p;
  guint                h;

  h = (guint) key->size << 5;

  for (p = key->name; *p != '\0'; ++p)
    h = (h << 5) - h + *p;

  if (key->symbolic)
    h ^= 1;

  if (key->color_hash != 0)
    h ^= key->color_hash;

  return h;
}



static gboolean
thunar_icon_key_equal (gconstpointer a,
                       gconstpointer b)
{
  const ThunarIconKey *a_key = a;
  const ThunarIconKey *b_key = b;

  /* compare sizes first */
  if (a_key->size != b_key->size)
    return FALSE;
  if (a_key->scale_factor != b_key->scale_factor)
    return FALSE;
  if (a_key->symbolic != b_key->symbolic)
    return FALSE;
  if (a_key->color_hash != b_key->color_hash)
    return FALSE;

  /* do a full string comparison on the names */
  return (g_strcmp0 (a_key->name, b_key->name) == 0);
}



static void
thunar_icon_key_free (gpointer data)
{
  ThunarIconKey *key = data;

  g_free (key->name);
  g_slice_free (ThunarIconKey, key);
}



static void
thunar_icon_store_free (gpointer data)
{
  ThunarIconStore *store = data;

  if (store->icon != NULL)
    g_object_unref (store->icon);
  g_slice_free (ThunarIconStore, store);
}



static GdkPixbuf *
thunar_icon_factory_load_fallback (ThunarIconFactory *factory,
                                   gint               size,
                                   gint               scale_factor,
                                   gboolean           symbolic)
{
  return thunar_icon_factory_lookup_icon (factory, "text-x-generic", size, scale_factor, FALSE, symbolic, NULL);
}



/**
 * thunar_icon_factory_get_default:
 *
 * Returns the #ThunarIconFactory that operates on the default #GtkIconTheme.
 * The default #ThunarIconFactory instance will be around for the time the
 * programs runs, starting with the first call to this function.
 *
 * The caller is responsible to free the returned object using
 * g_object_unref() when no longer needed.
 *
 * Return value: the #ThunarIconFactory for the default icon theme.
 **/
ThunarIconFactory *
thunar_icon_factory_get_default (void)
{
  static ThunarIconFactory *factory = NULL;

  if (G_UNLIKELY (factory == NULL))
    {
      factory = thunar_icon_factory_get_for_icon_theme (gtk_icon_theme_get_default ());
      g_object_add_weak_pointer (G_OBJECT (factory), (gpointer) &factory);
    }
  else
    {
      g_object_ref (G_OBJECT (factory));
    }

  return factory;
}



/**
 * thunar_icon_factory_get_for_icon_theme:
 * @icon_theme : a #GtkIconTheme instance.
 *
 * Determines the proper #ThunarIconFactory to be used with the specified
 * @icon_theme and returns it.
 *
 * You need to explicitly free the returned #ThunarIconFactory object
 * using g_object_unref() when you are done with it.
 *
 * Return value: the #ThunarIconFactory for @icon_theme.
 **/
ThunarIconFactory *
thunar_icon_factory_get_for_icon_theme (GtkIconTheme *icon_theme)
{
  ThunarIconFactory *factory;

  _thunar_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL);

  /* generate the quark on-demand */
  if (G_UNLIKELY (thunar_icon_factory_quark == 0))
    thunar_icon_factory_quark = g_quark_from_static_string ("thunar-icon-factory");

  /* check if the given icon theme already knows about an icon factory */
  factory = g_object_get_qdata (G_OBJECT (icon_theme), thunar_icon_factory_quark);
  if (G_UNLIKELY (factory == NULL))
    {
      /* allocate a new factory and connect it to the icon theme */
      factory = g_object_new (THUNAR_TYPE_ICON_FACTORY, NULL);
      factory->icon_theme = GTK_ICON_THEME (g_object_ref (G_OBJECT (icon_theme)));
      g_object_set_qdata (G_OBJECT (factory->icon_theme), thunar_icon_factory_quark, factory);

      /* connect the properties of the global preference */
      factory->preferences = thunar_preferences_get ();
      g_object_bind_property (G_OBJECT (factory->preferences), "misc-thumbnail-mode",
                              G_OBJECT (factory), "thumbnail-mode",
                              G_BINDING_SYNC_CREATE);
      g_object_bind_property (G_OBJECT (factory->preferences), "misc-thumbnail-max-file-size",
                              G_OBJECT (factory), "thumbnail-max-file-size",
                              G_BINDING_SYNC_CREATE);
    }
  else
    {
      g_object_ref (G_OBJECT (factory));
    }

  return factory;
}



/**
 * thunar_icon_factory_get_show_thumbnail:
 * @factory       : a #ThunarIconFactory instance.
 * @file          : a #ThunarFile.
 *
 * Return value: if a Thumbnail show be shown for @file.
 **/
gboolean
thunar_icon_factory_get_show_thumbnail (const ThunarIconFactory *factory,
                                        const ThunarFile        *file)
{
  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), THUNAR_THUMBNAIL_MODE_NEVER);
  _thunar_return_val_if_fail (file == NULL || THUNAR_IS_FILE (file), THUNAR_THUMBNAIL_MODE_NEVER);

  if (file == NULL || factory->thumbnail_mode == THUNAR_THUMBNAIL_MODE_NEVER)
    return FALSE;

  /*
   * Note: We ignore 'G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW' on purpose,
   * since following the glib suggestion would lead to having no thumbnails at all for mtp and gphoto2 devices.
   * Instead, settings 'THUNAR_THUMBNAIL_MODE_ALWAYS' will indeed always show thumbnails.
   */
  if (factory->thumbnail_mode == THUNAR_THUMBNAIL_MODE_ALWAYS)
    return TRUE;

  if (factory->thumbnail_mode == THUNAR_THUMBNAIL_MODE_ONLY_LOCAL && thunar_file_is_local (file))
    return TRUE;

  return FALSE;
}



/**
 * thunar_icon_factory_load_icon:
 * @factory       : a #ThunarIconFactory instance.
 * @name          : name of the icon to load.
 * @size          : desired icon size.
 * @scale_factor  : the UI scale factor.
 * @wants_default : %TRUE to return the fallback icon if no icon of @name
 *                  is found in the @factory.
 * @symbolic      : %TRUE to request a symbolic icon if possible.
 * @context       : a #GtkStyleContext instance, can be %NULL.
 *
 * Looks up the icon named @name in the icon theme associated with @factory
 * and returns a pixbuf for the icon at the given @size. This function will
 * return a default fallback icon if the requested icon could not be found
 * and @wants_default is %TRUE, else %NULL will be returned in that case.
 *
 * If @symbolic is %TRUE, try to load a symbolic icon that optionally
 * matches the system colors provided by @context.
 *
 * Call g_object_unref() on the returned pixbuf when you are
 * done with it.
 *
 * Return value: the pixbuf for the icon named @name at @size.
 **/
GdkPixbuf *
thunar_icon_factory_load_icon (ThunarIconFactory *factory,
                               const gchar       *name,
                               gint               size,
                               gint               scale_factor,
                               gboolean           wants_default,
                               gboolean           symbolic,
                               GtkStyleContext   *context)
{
  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
  _thunar_return_val_if_fail (size > 0, NULL);

  /* cannot happen unless there's no XSETTINGS manager
   * for the default screen, but just in case...
   */
  if (G_UNLIKELY (xfce_str_is_empty (name)))
    {
      /* check if the caller will happly accept the fallback icon */
      if (G_LIKELY (wants_default))
        return thunar_icon_factory_load_fallback (factory, size, scale_factor, symbolic);
      else
        return NULL;
    }

  /* lookup the icon */
  return thunar_icon_factory_lookup_icon (factory, name, size, scale_factor, wants_default, symbolic, context);
}



/**
 * thunar_icon_factory_load_file_icon:
 * @factory      : a #ThunarIconFactory instance.
 * @file         : a #ThunarFile.
 * @icon_state   : the desired icon state.
 * @icon_size    : the desired icon size.
 * @scale_factor : the UI scale factor.
 * @symbolic     : load the symbolic version of the icon.
 * @context      : a #GtkStyleContext instance, can be %NULL.
 *
 * If @symbolic is %TRUE, try to load a symbolic icon that optionally
 * matches the system colors provided by @context.
 *
 * The caller is responsible to free the returned object using
 * g_object_unref() when no longer needed.
 *
 * Return value: the #GdkPixbuf icon.
 **/
GdkPixbuf *
thunar_icon_factory_load_file_icon (ThunarIconFactory  *factory,
                                    ThunarFile         *file,
                                    ThunarFileIconState icon_state,
                                    gint                icon_size,
                                    gint                scale_factor,
                                    gboolean            symbolic,
                                    GtkStyleContext    *context)
{
  GInputStream    *stream;
  GtkIconInfo     *icon_info;
  const gchar     *thumbnail_path;
  GdkPixbuf       *icon = NULL;
  GIcon           *gicon;
  const gchar     *icon_name;
  const gchar     *custom_icon;
  ThunarIconStore *store;

  _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
  _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
  _thunar_return_val_if_fail (icon_size > 0, NULL);

  /* check if we have a stored icon on the file and it is still valid */
  store = g_object_get_qdata (G_OBJECT (file), thunar_icon_factory_store_quark);
  if (store != NULL
      && store->icon_state == icon_state
      && store->icon_size == icon_size
      && store->stamp == factory->theme_stamp
      && store->thumbnail_draw_frames == factory->thumbnail_draw_frames
      && store->thumb_state == thunar_file_get_thumb_state (file, thunar_icon_size_to_thumbnail_size (icon_size * scale_factor))
      && store->symbolic == symbolic)
    {
      return g_object_ref (store->icon);
    }

  /* check if we have a custom icon for this file */
  custom_icon = thunar_file_get_custom_icon (file);
  if (custom_icon != NULL)
    {
      /* try to load the icon */
      icon = thunar_icon_factory_lookup_icon (factory, custom_icon, icon_size, scale_factor,
                                              FALSE, symbolic, context);
      if (G_LIKELY (icon != NULL))
        return icon;
    }

  /* check if thumbnails are enabled and we can display a thumbnail for the item */
  if (thunar_icon_factory_get_show_thumbnail (factory, file))
    {
      /* determine the preview icon first */
      gicon = thunar_file_get_preview_icon (file);

      /* check if we have a preview icon */
      if (gicon != NULL)
        {
          if (G_IS_THEMED_ICON (gicon))
            {
              /* we have a themed preview icon, look it up using the icon theme */
              icon_info =
              gtk_icon_theme_lookup_by_gicon_for_scale (factory->icon_theme,
                                                        gicon,
                                                        icon_size,
                                                        scale_factor,
                                                        GTK_ICON_LOOKUP_USE_BUILTIN
                                                        | GTK_ICON_LOOKUP_FORCE_SIZE);

              /* check if the lookup succeeded */
              if (icon_info != NULL)
                {
                  /* try to load the pixbuf from the icon info */
                  icon = gtk_icon_info_load_icon (icon_info, NULL);
                  g_object_unref (icon_info);
                }
            }
          else if (G_IS_LOADABLE_ICON (gicon))
            {
              /* we have a loadable icon, try to open it for reading */
              stream = g_loadable_icon_load (G_LOADABLE_ICON (gicon), icon_size,
                                             NULL, NULL, NULL);

              /* check if we have a valid input stream */
              if (stream != NULL)
                {
                  /* load the pixbuf from the stream */
                  icon = gdk_pixbuf_new_from_stream_at_scale (stream,
                                                              icon_size * scale_factor,
                                                              icon_size * scale_factor, TRUE,
                                                              NULL, NULL);

                  /* destroy the stream */
                  g_object_unref (stream);
                }
            }

          /* return the icon if we have one */
          if (icon != NULL)
            return icon;
        }
      else
        {
          if (thunar_file_get_size (file) < factory->thumbnail_max_file_size || factory->thumbnail_max_file_size == 0) /* 0 = unlimited */
            {
              /* we have no preview icon but the thumbnail should be ready. determine
               * the filename of the thumbnail */
              thumbnail_path = thunar_file_get_thumbnail_path (file, thunar_icon_size_to_thumbnail_size (icon_size * scale_factor));

              /* check if we have a valid path */
              if (thumbnail_path != NULL)
                /* try to load the thumbnail */
                icon = thunar_icon_factory_load_from_file (factory, thumbnail_path, icon_size, scale_factor);
            }
        }
    }

  /* lookup the icon name for the icon in the given state and load the icon */
  if (G_LIKELY (icon == NULL))
    {
      icon_name = thunar_file_get_icon_name (file, icon_state, factory->icon_theme);
      icon = thunar_icon_factory_load_icon (factory, icon_name, icon_size, scale_factor,
                                            TRUE, symbolic, context);
    }

  /* skip icon store */
  if (symbolic && context != NULL)
    return icon;

  if (G_LIKELY (icon != NULL))
    {
      store = g_slice_new (ThunarIconStore);
      store->icon_size = icon_size;
      store->icon_state = icon_state;
      store->stamp = factory->theme_stamp;
      store->thumbnail_draw_frames = factory->thumbnail_draw_frames;
      store->thumb_state = thunar_file_get_thumb_state (file, thunar_icon_size_to_thumbnail_size (icon_size * scale_factor));
      store->symbolic = symbolic;
      store->icon = g_object_ref (icon);

      g_object_set_qdata_full (G_OBJECT (file), thunar_icon_factory_store_quark,
                               store, thunar_icon_store_free);
    }

  return icon;
}



/**
 * thunar_icon_factory_clear_pixmap_cache:
 * @file : a #ThunarFile.
 *
 * Unset the pixmap cache on a file to force a reload on the next request.
 **/
void
thunar_icon_factory_clear_pixmap_cache (ThunarFile *file)
{
  _thunar_return_if_fail (THUNAR_IS_FILE (file));

  /* unset the data */
  if (thunar_icon_factory_store_quark != 0)
    g_object_set_qdata (G_OBJECT (file), thunar_icon_factory_store_quark, NULL);
}
