Tree of tag thunar-4.20.2 (commit 11a519f) from https://gitlab.xfce.org/xfce/thunar. Upstream history is not included here to keep the repository small.
1130 lines
36 KiB
C
1130 lines
36 KiB
C
/* 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);
|
|
}
|