1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
4 * Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #ifdef HAVE_MEMORY_H
27 #include <memory.h>
28 #endif
29 #ifdef HAVE_STRING_H
30 #include <string.h>
31 #endif
32
33 #include <thunar/thunar-gobject-extensions.h>
34 #include <thunar/thunar-icon-factory.h>
35 #include <thunar/thunar-preferences.h>
36 #include <thunar/thunar-private.h>
37 #include <thunar/thunar-util.h>
38
39
40
41 /* the timeout until the sweeper is run (in seconds) */
42 #define THUNAR_ICON_FACTORY_SWEEP_TIMEOUT (30)
43
44
45
46 /* Property identifiers */
47 enum
48 {
49 PROP_0,
50 PROP_ICON_THEME,
51 PROP_THUMBNAIL_MODE,
52 PROP_THUMBNAIL_DRAW_FRAMES,
53 PROP_THUMBNAIL_SIZE,
54 };
55
56
57
58 typedef struct _ThunarIconKey ThunarIconKey;
59
60
61
62 static void thunar_icon_factory_dispose (GObject *object);
63 static void thunar_icon_factory_finalize (GObject *object);
64 static void thunar_icon_factory_get_property (GObject *object,
65 guint prop_id,
66 GValue *value,
67 GParamSpec *pspec);
68 static void thunar_icon_factory_set_property (GObject *object,
69 guint prop_id,
70 const GValue *value,
71 GParamSpec *pspec);
72 static gboolean thunar_icon_factory_changed (GSignalInvocationHint *ihint,
73 guint n_param_values,
74 const GValue *param_values,
75 gpointer user_data);
76 static gboolean thunar_icon_factory_sweep_timer (gpointer user_data);
77 static void thunar_icon_factory_sweep_timer_destroy (gpointer user_data);
78 static GdkPixbuf *thunar_icon_factory_load_from_file (ThunarIconFactory *factory,
79 const gchar *path,
80 gint size);
81 static GdkPixbuf *thunar_icon_factory_lookup_icon (ThunarIconFactory *factory,
82 const gchar *name,
83 gint size,
84 gboolean wants_default);
85 static guint thunar_icon_key_hash (gconstpointer data);
86 static gboolean thunar_icon_key_equal (gconstpointer a,
87 gconstpointer b);
88 static void thunar_icon_key_free (gpointer data);
89 static GdkPixbuf *thunar_icon_factory_load_fallback (ThunarIconFactory *factory,
90 gint size);
91
92
93
94 struct _ThunarIconFactoryClass
95 {
96 GObjectClass __parent__;
97 };
98
99 struct _ThunarIconFactory
100 {
101 GObject __parent__;
102
103 ThunarPreferences *preferences;
104
105 GHashTable *icon_cache;
106
107 GtkIconTheme *icon_theme;
108
109 ThunarThumbnailMode thumbnail_mode;
110
111 gboolean thumbnail_draw_frames;
112
113 ThunarThumbnailSize thumbnail_size;
114
115 guint sweep_timer_id;
116
117 gulong changed_hook_id;
118
119 /* stamp that gets bumped when the theme changes */
120 guint theme_stamp;
121 };
122
123 struct _ThunarIconKey
124 {
125 gchar *name;
126 gint size;
127 };
128
129 typedef struct
130 {
131 ThunarFileIconState icon_state;
132 ThunarFileThumbState thumb_state;
133 gint icon_size;
134 guint stamp;
135 GdkPixbuf *icon;
136 }
137 ThunarIconStore;
138
139
140
141 static GQuark thunar_icon_factory_quark = 0;
142 static GQuark thunar_icon_factory_store_quark = 0;
143
144
145
G_DEFINE_TYPE(ThunarIconFactory,thunar_icon_factory,G_TYPE_OBJECT)146 G_DEFINE_TYPE (ThunarIconFactory, thunar_icon_factory, G_TYPE_OBJECT)
147
148
149
150 static void
151 thunar_icon_factory_class_init (ThunarIconFactoryClass *klass)
152 {
153 GObjectClass *gobject_class;
154
155 thunar_icon_factory_store_quark = g_quark_from_static_string ("thunar-icon-factory-store");
156
157 gobject_class = G_OBJECT_CLASS (klass);
158 gobject_class->dispose = thunar_icon_factory_dispose;
159 gobject_class->finalize = thunar_icon_factory_finalize;
160 gobject_class->get_property = thunar_icon_factory_get_property;
161 gobject_class->set_property = thunar_icon_factory_set_property;
162
163 /**
164 * ThunarIconFactory:icon-theme:
165 *
166 * The #GtkIconTheme on which the given #ThunarIconFactory instance operates
167 * on.
168 **/
169 g_object_class_install_property (gobject_class,
170 PROP_ICON_THEME,
171 g_param_spec_object ("icon-theme",
172 "icon-theme",
173 "icon-theme",
174 GTK_TYPE_ICON_THEME,
175 EXO_PARAM_READABLE));
176
177 /**
178 * ThunarIconFactory:thumbnail-mode:
179 *
180 * Whether this #ThunarIconFactory will try to generate and load thumbnails
181 * when loading icons for #ThunarFile<!---->s.
182 **/
183 g_object_class_install_property (gobject_class,
184 PROP_THUMBNAIL_MODE,
185 g_param_spec_enum ("thumbnail-mode",
186 "thumbnail-mode",
187 "thumbnail-mode",
188 THUNAR_TYPE_THUMBNAIL_MODE,
189 THUNAR_THUMBNAIL_MODE_ONLY_LOCAL,
190 EXO_PARAM_READWRITE));
191
192 /**
193 * ThunarIconFactory:thumbnail-draw-frames:
194 *
195 * Whether to draw black frames around thumbnails.
196 * This looks neat, but will delay the first draw a bit.
197 * May have an impact on older systems, on folders with many pictures.
198 **/
199 g_object_class_install_property (gobject_class,
200 PROP_THUMBNAIL_DRAW_FRAMES,
201 g_param_spec_boolean ("thumbnail-draw-frames",
202 "thumbnail-draw-frames",
203 "thumbnail-draw-frames",
204 FALSE,
205 EXO_PARAM_READWRITE));
206
207 /**
208 * ThunarIconFactory:thumbnail-size:
209 *
210 * Size of the thumbnails to load
211 **/
212 g_object_class_install_property (gobject_class,
213 PROP_THUMBNAIL_SIZE,
214 g_param_spec_enum ("thumbnail-size",
215 "thumbnail-size",
216 "thumbnail-size",
217 THUNAR_TYPE_THUMBNAIL_SIZE,
218 THUNAR_THUMBNAIL_SIZE_NORMAL,
219 EXO_PARAM_READWRITE));
220 }
221
222
223
224 static void
thunar_icon_factory_init(ThunarIconFactory * factory)225 thunar_icon_factory_init (ThunarIconFactory *factory)
226 {
227 factory->thumbnail_mode = THUNAR_THUMBNAIL_MODE_ONLY_LOCAL;
228 factory->thumbnail_size = THUNAR_THUMBNAIL_SIZE_NORMAL;
229
230 /* connect emission hook for the "changed" signal on the GtkIconTheme class. We use the emission
231 * hook way here, because that way we can make sure that the icon cache is definetly cleared
232 * before any other part of the application gets notified about the icon theme change.
233 */
234 factory->changed_hook_id = g_signal_add_emission_hook (g_signal_lookup ("changed", GTK_TYPE_ICON_THEME),
235 0, thunar_icon_factory_changed, factory, NULL);
236
237 /* allocate the hash table for the icon cache */
238 factory->icon_cache = g_hash_table_new_full (thunar_icon_key_hash, thunar_icon_key_equal,
239 thunar_icon_key_free, g_object_unref);
240 }
241
242
243
244 static void
thunar_icon_factory_dispose(GObject * object)245 thunar_icon_factory_dispose (GObject *object)
246 {
247 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);
248
249 _thunar_return_if_fail (THUNAR_IS_ICON_FACTORY (factory));
250
251 if (G_UNLIKELY (factory->sweep_timer_id != 0))
252 g_source_remove (factory->sweep_timer_id);
253
254 (*G_OBJECT_CLASS (thunar_icon_factory_parent_class)->dispose) (object);
255 }
256
257
258
259 static void
thunar_icon_factory_finalize(GObject * object)260 thunar_icon_factory_finalize (GObject *object)
261 {
262 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);
263
264 _thunar_return_if_fail (THUNAR_IS_ICON_FACTORY (factory));
265
266 /* clear the icon cache hash table */
267 g_hash_table_destroy (factory->icon_cache);
268
269 /* remove the "changed" emission hook from the GtkIconTheme class */
270 g_signal_remove_emission_hook (g_signal_lookup ("changed", GTK_TYPE_ICON_THEME), factory->changed_hook_id);
271
272 /* disconnect from the associated icon theme (if any) */
273 if (G_LIKELY (factory->icon_theme != NULL))
274 {
275 g_object_set_qdata (G_OBJECT (factory->icon_theme), thunar_icon_factory_quark, NULL);
276 g_object_unref (G_OBJECT (factory->icon_theme));
277 }
278
279 /* disconnect from the preferences */
280 g_object_unref (G_OBJECT (factory->preferences));
281
282 (*G_OBJECT_CLASS (thunar_icon_factory_parent_class)->finalize) (object);
283 }
284
285
286
287 static void
thunar_icon_factory_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)288 thunar_icon_factory_get_property (GObject *object,
289 guint prop_id,
290 GValue *value,
291 GParamSpec *pspec)
292 {
293 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);
294
295 switch (prop_id)
296 {
297 case PROP_ICON_THEME:
298 g_value_set_object (value, factory->icon_theme);
299 break;
300
301 case PROP_THUMBNAIL_MODE:
302 g_value_set_enum (value, factory->thumbnail_mode);
303 break;
304
305 case PROP_THUMBNAIL_DRAW_FRAMES:
306 g_value_set_boolean (value, factory->thumbnail_draw_frames);
307 break;
308
309 case PROP_THUMBNAIL_SIZE:
310 g_value_set_enum (value, factory->thumbnail_size);
311 break;
312
313 default:
314 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
315 break;
316 }
317 }
318
319
320
321 static void
thunar_icon_factory_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)322 thunar_icon_factory_set_property (GObject *object,
323 guint prop_id,
324 const GValue *value,
325 GParamSpec *pspec)
326 {
327 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (object);
328
329 switch (prop_id)
330 {
331 case PROP_THUMBNAIL_MODE:
332 factory->thumbnail_mode = g_value_get_enum (value);
333 break;
334
335 case PROP_THUMBNAIL_DRAW_FRAMES:
336 factory->thumbnail_draw_frames = g_value_get_boolean (value);
337 break;
338
339 case PROP_THUMBNAIL_SIZE:
340 factory->thumbnail_size = g_value_get_enum (value);
341 break;
342
343 default:
344 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
345 break;
346 }
347 }
348
349
350
351 static gboolean
thunar_icon_factory_changed(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer user_data)352 thunar_icon_factory_changed (GSignalInvocationHint *ihint,
353 guint n_param_values,
354 const GValue *param_values,
355 gpointer user_data)
356 {
357 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (user_data);
358
359 /* drop all items from the icon cache */
360 g_hash_table_remove_all (factory->icon_cache);
361
362 /* bump the stamp so all file icons are reloaded */
363 factory->theme_stamp++;
364
365 /* keep the emission hook alive */
366 return TRUE;
367 }
368
369
370
371 static gboolean
thunar_icon_check_sweep(ThunarIconKey * key,GdkPixbuf * pixbuf)372 thunar_icon_check_sweep (ThunarIconKey *key,
373 GdkPixbuf *pixbuf)
374 {
375 return (G_OBJECT (pixbuf)->ref_count == 1);
376 }
377
378
379
380 static gboolean
thunar_icon_factory_sweep_timer(gpointer user_data)381 thunar_icon_factory_sweep_timer (gpointer user_data)
382 {
383 ThunarIconFactory *factory = THUNAR_ICON_FACTORY (user_data);
384
385 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), FALSE);
386
387 THUNAR_THREADS_ENTER
388
389 /* ditch all icons whose ref_count is 1 */
390 g_hash_table_foreach_remove (factory->icon_cache,
391 (GHRFunc) (void (*)(void)) thunar_icon_check_sweep,
392 factory);
393
394 THUNAR_THREADS_LEAVE
395
396 return FALSE;
397 }
398
399
400
401 static void
thunar_icon_factory_sweep_timer_destroy(gpointer user_data)402 thunar_icon_factory_sweep_timer_destroy (gpointer user_data)
403 {
404 THUNAR_ICON_FACTORY (user_data)->sweep_timer_id = 0;
405 }
406
407
408
409 static inline gboolean
thumbnail_needs_frame(const GdkPixbuf * thumbnail,gint width,gint height,gint size)410 thumbnail_needs_frame (const GdkPixbuf *thumbnail,
411 gint width,
412 gint height,
413 gint size)
414 {
415 const guchar *pixels;
416 gint rowstride;
417 gint n;
418
419 /* don't add frames to small thumbnails */
420 if (size < THUNAR_ICON_SIZE_64 )
421 return FALSE;
422
423 /* always add a frame to thumbnails w/o alpha channel */
424 if (G_LIKELY (!gdk_pixbuf_get_has_alpha (thumbnail)))
425 return TRUE;
426
427 /* get a pointer to the thumbnail data */
428 pixels = gdk_pixbuf_get_pixels (thumbnail);
429
430 /* check if we have a transparent pixel on the first row */
431 for (n = width * 4; n > 0; n -= 4)
432 if (pixels[n - 1] < 255u)
433 return FALSE;
434
435 /* determine the rowstride */
436 rowstride = gdk_pixbuf_get_rowstride (thumbnail);
437
438 /* skip the first row */
439 pixels += rowstride;
440
441 /* check if we have a transparent pixel in the first or last column */
442 for (n = height - 2; n > 0; --n, pixels += rowstride)
443 if (pixels[3] < 255u || pixels[width * 4 - 1] < 255u)
444 return FALSE;
445
446 /* check if we have a transparent pixel on the last row */
447 for (n = width * 4; n > 0; n -= 4)
448 if (pixels[n - 1] < 255u)
449 return FALSE;
450
451 return TRUE;
452 }
453
454
455
456 static GdkPixbuf*
thunar_icon_factory_get_thumbnail_frame(void)457 thunar_icon_factory_get_thumbnail_frame (void)
458 {
459 GInputStream *stream;
460 static GdkPixbuf *frame = NULL;
461
462 if (G_LIKELY (frame != NULL))
463 return frame;
464
465 stream = g_resources_open_stream ("/org/xfce/thunar/thumbnail-frame.png", 0, NULL);
466 if (G_UNLIKELY (stream != NULL)) {
467 frame = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
468 g_object_unref (stream);
469 }
470
471 return frame;
472 }
473
474
475
476 static GdkPixbuf*
thunar_icon_factory_load_from_file(ThunarIconFactory * factory,const gchar * path,gint size)477 thunar_icon_factory_load_from_file (ThunarIconFactory *factory,
478 const gchar *path,
479 gint size)
480 {
481 GdkPixbuf *pixbuf;
482 GdkPixbuf *frame;
483 GdkPixbuf *tmp;
484 gboolean needs_frame;
485 gint max_width;
486 gint max_height;
487 gint width;
488 gint height;
489
490 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
491
492 /* try to load the image from the file */
493 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
494 if (G_LIKELY (pixbuf != NULL))
495 {
496 /* determine the dimensions of the pixbuf */
497 width = gdk_pixbuf_get_width (pixbuf);
498 height = gdk_pixbuf_get_height (pixbuf);
499
500 needs_frame = FALSE;
501 if (factory->thumbnail_draw_frames)
502 {
503 /* check if we want to add a frame to the image (we really don't
504 * want to do this for icons displayed in the details view).
505 * */
506 needs_frame = (strstr (path, G_DIR_SEPARATOR_S ".cache/thumbnails" G_DIR_SEPARATOR_S) != NULL)
507 && (size >= 32) && thumbnail_needs_frame (pixbuf, width, height, size);
508 }
509
510 /* be sure to make framed thumbnails fit into the size */
511 if (G_LIKELY (needs_frame))
512 {
513 max_width = size - (3 + 6);
514 max_height = size - (3 + 6);
515 }
516 else
517 {
518 max_width = size;
519 max_height = size;
520 }
521
522 /* scale down the icon (if required) */
523 if (G_LIKELY (width > max_width || height > max_height))
524 {
525 /* scale down to the required size */
526 tmp = exo_gdk_pixbuf_scale_down (pixbuf, TRUE, MAX (1, max_height), MAX (1, max_height));
527 g_object_unref (G_OBJECT (pixbuf));
528 pixbuf = tmp;
529 }
530
531 /* add a frame around thumbnail (large) images */
532 if (G_LIKELY (needs_frame))
533 {
534 /* add a frame to the thumbnail */
535 frame = thunar_icon_factory_get_thumbnail_frame ();
536 tmp = exo_gdk_pixbuf_frame (pixbuf, frame, 4, 3, 5, 6);
537 g_object_unref (G_OBJECT (pixbuf));
538 pixbuf = tmp;
539 }
540 }
541
542 return pixbuf;
543 }
544
545
546
547 static GdkPixbuf*
thunar_icon_factory_lookup_icon(ThunarIconFactory * factory,const gchar * name,gint size,gboolean wants_default)548 thunar_icon_factory_lookup_icon (ThunarIconFactory *factory,
549 const gchar *name,
550 gint size,
551 gboolean wants_default)
552 {
553 ThunarIconKey lookup_key;
554 ThunarIconKey *key;
555 GtkIconInfo *icon_info;
556 GdkPixbuf *pixbuf = NULL;
557
558 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
559 _thunar_return_val_if_fail (name != NULL && *name != '\0', NULL);
560 _thunar_return_val_if_fail (size > 0, NULL);
561
562 /* prepare the lookup key */
563 lookup_key.name = (gchar *) name;
564 lookup_key.size = size;
565
566 /* check if we already have a cached version of the icon */
567 if (!g_hash_table_lookup_extended (factory->icon_cache, &lookup_key, NULL, (gpointer) &pixbuf))
568 {
569 /* check if we have to load a file instead of a themed icon */
570 if (G_UNLIKELY (g_path_is_absolute (name)))
571 {
572 /* load the file directly */
573 pixbuf = thunar_icon_factory_load_from_file (factory, name, size);
574 }
575 else
576 {
577 /* FIXME: is there a better approach? */
578 if (g_strcmp0 (name, "inode-directory") == 0)
579 name = "folder";
580
581 /* check if the icon theme contains an icon of that name */
582 icon_info = gtk_icon_theme_lookup_icon (factory->icon_theme, name, size, GTK_ICON_LOOKUP_FORCE_SIZE);
583 if (G_LIKELY (icon_info != NULL))
584 {
585 /* try to load the pixbuf from the icon info */
586 pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
587
588 /* cleanup */
589 g_object_unref (icon_info);
590 }
591 }
592
593 /* use fallback icon if no pixbuf could be loaded */
594 if (G_UNLIKELY (pixbuf == NULL))
595 {
596 /* check if we are allowed to return the fallback icon */
597 if (!wants_default)
598 return NULL;
599 else
600 return thunar_icon_factory_load_fallback (factory, size);
601 }
602
603 /* generate a key for the new cached icon */
604 key = g_slice_new (ThunarIconKey);
605 key->size = size;
606 key->name = g_strdup (name);
607
608 /* insert the new icon into the cache */
609 g_hash_table_insert (factory->icon_cache, key, pixbuf);
610 }
611
612 /* schedule the sweeper */
613 if (G_UNLIKELY (factory->sweep_timer_id == 0))
614 {
615 factory->sweep_timer_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, THUNAR_ICON_FACTORY_SWEEP_TIMEOUT,
616 thunar_icon_factory_sweep_timer, factory,
617 thunar_icon_factory_sweep_timer_destroy);
618 }
619
620 return GDK_PIXBUF (g_object_ref (G_OBJECT (pixbuf)));
621 }
622
623
624
625 static guint
thunar_icon_key_hash(gconstpointer data)626 thunar_icon_key_hash (gconstpointer data)
627 {
628 const ThunarIconKey *key = data;
629 const gchar *p;
630 guint h;
631
632 h = (guint) key->size << 5;
633
634 for (p = key->name; *p != '\0'; ++p)
635 h = (h << 5) - h + *p;
636
637 return h;
638 }
639
640
641
642 static gboolean
thunar_icon_key_equal(gconstpointer a,gconstpointer b)643 thunar_icon_key_equal (gconstpointer a,
644 gconstpointer b)
645 {
646 const ThunarIconKey *a_key = a;
647 const ThunarIconKey *b_key = b;
648
649 /* compare sizes first */
650 if (a_key->size != b_key->size)
651 return FALSE;
652
653 /* do a full string comparison on the names */
654 return exo_str_is_equal (a_key->name, b_key->name);
655 }
656
657
658
659 static void
thunar_icon_key_free(gpointer data)660 thunar_icon_key_free (gpointer data)
661 {
662 ThunarIconKey *key = data;
663
664 g_free (key->name);
665 g_slice_free (ThunarIconKey, key);
666 }
667
668
669
670 static void
thunar_icon_store_free(gpointer data)671 thunar_icon_store_free (gpointer data)
672 {
673 ThunarIconStore *store = data;
674
675 if (store->icon != NULL)
676 g_object_unref (store->icon);
677 g_slice_free (ThunarIconStore, store);
678 }
679
680
681
682 static GdkPixbuf*
thunar_icon_factory_load_fallback(ThunarIconFactory * factory,gint size)683 thunar_icon_factory_load_fallback (ThunarIconFactory *factory,
684 gint size)
685 {
686 return thunar_icon_factory_lookup_icon (factory, "text-x-generic", size, FALSE);
687 }
688
689
690
691 /**
692 * thunar_icon_factory_get_default:
693 *
694 * Returns the #ThunarIconFactory that operates on the default #GtkIconTheme.
695 * The default #ThunarIconFactory instance will be around for the time the
696 * programs runs, starting with the first call to this function.
697 *
698 * The caller is responsible to free the returned object using
699 * g_object_unref() when no longer needed.
700 *
701 * Return value: the #ThunarIconFactory for the default icon theme.
702 **/
703 ThunarIconFactory*
thunar_icon_factory_get_default(void)704 thunar_icon_factory_get_default (void)
705 {
706 static ThunarIconFactory *factory = NULL;
707
708 if (G_UNLIKELY (factory == NULL))
709 {
710 factory = thunar_icon_factory_get_for_icon_theme (gtk_icon_theme_get_default ());
711 g_object_add_weak_pointer (G_OBJECT (factory), (gpointer) &factory);
712 }
713 else
714 {
715 g_object_ref (G_OBJECT (factory));
716 }
717
718 return factory;
719 }
720
721
722
723 /**
724 * thunar_icon_factory_get_for_icon_theme:
725 * @icon_theme : a #GtkIconTheme instance.
726 *
727 * Determines the proper #ThunarIconFactory to be used with the specified
728 * @icon_theme and returns it.
729 *
730 * You need to explicitly free the returned #ThunarIconFactory object
731 * using g_object_unref() when you are done with it.
732 *
733 * Return value: the #ThunarIconFactory for @icon_theme.
734 **/
735 ThunarIconFactory*
thunar_icon_factory_get_for_icon_theme(GtkIconTheme * icon_theme)736 thunar_icon_factory_get_for_icon_theme (GtkIconTheme *icon_theme)
737 {
738 ThunarIconFactory *factory;
739
740 _thunar_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL);
741
742 /* generate the quark on-demand */
743 if (G_UNLIKELY (thunar_icon_factory_quark == 0))
744 thunar_icon_factory_quark = g_quark_from_static_string ("thunar-icon-factory");
745
746 /* check if the given icon theme already knows about an icon factory */
747 factory = g_object_get_qdata (G_OBJECT (icon_theme), thunar_icon_factory_quark);
748 if (G_UNLIKELY (factory == NULL))
749 {
750 /* allocate a new factory and connect it to the icon theme */
751 factory = g_object_new (THUNAR_TYPE_ICON_FACTORY, NULL);
752 factory->icon_theme = GTK_ICON_THEME (g_object_ref (G_OBJECT (icon_theme)));
753 g_object_set_qdata (G_OBJECT (factory->icon_theme), thunar_icon_factory_quark, factory);
754
755 /* connect the "show-thumbnails" property to the global preference */
756 factory->preferences = thunar_preferences_get ();
757 exo_binding_new (G_OBJECT (factory->preferences), "misc-thumbnail-mode",
758 G_OBJECT (factory), "thumbnail-mode");
759 }
760 else
761 {
762 g_object_ref (G_OBJECT (factory));
763 }
764
765 return factory;
766 }
767
768
769
770 /**
771 * thunar_icon_factory_get_show_thumbnail:
772 * @factory : a #ThunarIconFactory instance.
773 * @file : a #ThunarFile.
774 *
775 * Return value: if a Thumbnail show be shown for @file.
776 **/
777 gboolean
thunar_icon_factory_get_show_thumbnail(const ThunarIconFactory * factory,const ThunarFile * file)778 thunar_icon_factory_get_show_thumbnail (const ThunarIconFactory *factory,
779 const ThunarFile *file)
780 {
781 GFilesystemPreviewType preview;
782
783 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), THUNAR_THUMBNAIL_MODE_NEVER);
784 _thunar_return_val_if_fail (file == NULL || THUNAR_IS_FILE (file), THUNAR_THUMBNAIL_MODE_NEVER);
785
786 if (file == NULL
787 || factory->thumbnail_mode == THUNAR_THUMBNAIL_MODE_NEVER)
788 return FALSE;
789
790 /* always create thumbs for local files */
791 if (thunar_file_is_local (file))
792 return TRUE;
793
794 preview = thunar_file_get_preview_type (file);
795
796 /* file system says to never thumbnail anything */
797 if (preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
798 return FALSE;
799
800 /* only if the setting is local and the fs reports to be local */
801 if (factory->thumbnail_mode == THUNAR_THUMBNAIL_MODE_ONLY_LOCAL)
802 return preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL;
803
804 /* THUNAR_THUMBNAIL_MODE_ALWAYS */
805 return TRUE;
806 }
807
808
809
810 /**
811 * thunar_icon_factory_load_icon:
812 * @factory : a #ThunarIconFactory instance.
813 * @name : name of the icon to load.
814 * @size : desired icon size.
815 * @wants_default : %TRUE to return the fallback icon if no icon of @name
816 * is found in the @factory.
817 *
818 * Looks up the icon named @name in the icon theme associated with @factory
819 * and returns a pixbuf for the icon at the given @size. This function will
820 * return a default fallback icon if the requested icon could not be found
821 * and @wants_default is %TRUE, else %NULL will be returned in that case.
822 *
823 * Call g_object_unref() on the returned pixbuf when you are
824 * done with it.
825 *
826 * Return value: the pixbuf for the icon named @name at @size.
827 **/
828 GdkPixbuf*
thunar_icon_factory_load_icon(ThunarIconFactory * factory,const gchar * name,gint size,gboolean wants_default)829 thunar_icon_factory_load_icon (ThunarIconFactory *factory,
830 const gchar *name,
831 gint size,
832 gboolean wants_default)
833 {
834 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
835 _thunar_return_val_if_fail (size > 0, NULL);
836
837 /* cannot happen unless there's no XSETTINGS manager
838 * for the default screen, but just in case...
839 */
840 if (G_UNLIKELY (exo_str_is_empty (name)))
841 {
842 /* check if the caller will happly accept the fallback icon */
843 if (G_LIKELY (wants_default))
844 return thunar_icon_factory_load_fallback (factory, size);
845 else
846 return NULL;
847 }
848
849 /* lookup the icon */
850 return thunar_icon_factory_lookup_icon (factory, name, size, wants_default);
851 }
852
853
854
855 /**
856 * thunar_icon_factory_load_file_icon:
857 * @factory : a #ThunarIconFactory instance.
858 * @file : a #ThunarFile.
859 * @icon_state : the desired icon state.
860 * @icon_size : the desired icon size.
861 *
862 * The caller is responsible to free the returned object using
863 * g_object_unref() when no longer needed.
864 *
865 * Return value: the #GdkPixbuf icon.
866 **/
867 GdkPixbuf*
thunar_icon_factory_load_file_icon(ThunarIconFactory * factory,ThunarFile * file,ThunarFileIconState icon_state,gint icon_size)868 thunar_icon_factory_load_file_icon (ThunarIconFactory *factory,
869 ThunarFile *file,
870 ThunarFileIconState icon_state,
871 gint icon_size)
872 {
873 GInputStream *stream;
874 GtkIconInfo *icon_info;
875 const gchar *thumbnail_path;
876 GdkPixbuf *icon = NULL;
877 GIcon *gicon;
878 const gchar *icon_name;
879 const gchar *custom_icon;
880 ThunarIconStore *store;
881
882 _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL);
883 _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
884 _thunar_return_val_if_fail (icon_size > 0, NULL);
885
886 /* check if we have a stored icon on the file and it is still valid */
887 store = g_object_get_qdata (G_OBJECT (file), thunar_icon_factory_store_quark);
888 if (store != NULL
889 && store->icon_state == icon_state
890 && store->icon_size == icon_size
891 && store->stamp == factory->theme_stamp
892 && store->thumb_state == thunar_file_get_thumb_state (file))
893 {
894 return g_object_ref (store->icon);
895 }
896
897 /* check if we have a custom icon for this file */
898 custom_icon = thunar_file_get_custom_icon (file);
899 if (custom_icon != NULL)
900 {
901 /* try to load the icon */
902 icon = thunar_icon_factory_lookup_icon (factory, custom_icon, icon_size, FALSE);
903 if (G_LIKELY (icon != NULL))
904 return icon;
905 }
906
907 /* check if thumbnails are enabled and we can display a thumbnail for the item */
908 if (thunar_icon_factory_get_show_thumbnail (factory, file)
909 && (thunar_file_is_regular (file) || thunar_file_is_directory (file)) )
910 {
911 /* determine the preview icon first */
912 gicon = thunar_file_get_preview_icon (file);
913
914 /* check if we have a preview icon */
915 if (gicon != NULL)
916 {
917 if (G_IS_THEMED_ICON (gicon))
918 {
919 /* we have a themed preview icon, look it up using the icon theme */
920 icon_info =
921 gtk_icon_theme_lookup_by_gicon (factory->icon_theme,
922 gicon, icon_size,
923 GTK_ICON_LOOKUP_USE_BUILTIN
924 | GTK_ICON_LOOKUP_FORCE_SIZE);
925
926 /* check if the lookup succeeded */
927 if (icon_info != NULL)
928 {
929 /* try to load the pixbuf from the icon info */
930 icon = gtk_icon_info_load_icon (icon_info, NULL);
931 g_object_unref (icon_info);
932 }
933 }
934 else if (G_IS_LOADABLE_ICON (gicon))
935 {
936 /* we have a loadable icon, try to open it for reading */
937 stream = g_loadable_icon_load (G_LOADABLE_ICON (gicon), icon_size,
938 NULL, NULL, NULL);
939
940 /* check if we have a valid input stream */
941 if (stream != NULL)
942 {
943 /* load the pixbuf from the stream */
944 icon = gdk_pixbuf_new_from_stream_at_scale (stream, icon_size,
945 icon_size, TRUE,
946 NULL, NULL);
947
948 /* destroy the stream */
949 g_object_unref (stream);
950 }
951 }
952
953 /* return the icon if we have one */
954 if (icon != NULL)
955 return icon;
956 }
957 else
958 {
959 /* we have no preview icon but the thumbnail should be ready. determine
960 * the filename of the thumbnail */
961 thumbnail_path = thunar_file_get_thumbnail_path (file, factory->thumbnail_size);
962
963 /* check if we have a valid path */
964 if (thumbnail_path != NULL)
965 {
966 /* try to load the thumbnail */
967 icon = thunar_icon_factory_load_from_file (factory, thumbnail_path, icon_size);
968 }
969 }
970 }
971
972 /* lookup the icon name for the icon in the given state and load the icon */
973 if (G_LIKELY (icon == NULL))
974 {
975 icon_name = thunar_file_get_icon_name (file, icon_state, factory->icon_theme);
976 icon = thunar_icon_factory_load_icon (factory, icon_name, icon_size, TRUE);
977 }
978
979 if (G_LIKELY (icon != NULL))
980 {
981 store = g_slice_new (ThunarIconStore);
982 store->icon_size = icon_size;
983 store->icon_state = icon_state;
984 store->stamp = factory->theme_stamp;
985 store->thumb_state = thunar_file_get_thumb_state (file);
986 store->icon = g_object_ref (icon);
987
988 g_object_set_qdata_full (G_OBJECT (file), thunar_icon_factory_store_quark,
989 store, thunar_icon_store_free);
990 }
991
992 return icon;
993 }
994
995
996
997 /**
998 * thunar_icon_factory_clear_pixmap_cache:
999 * @file : a #ThunarFile.
1000 *
1001 * Unset the pixmap cache on a file to force a reload on the next request.
1002 **/
1003 void
thunar_icon_factory_clear_pixmap_cache(ThunarFile * file)1004 thunar_icon_factory_clear_pixmap_cache (ThunarFile *file)
1005 {
1006 _thunar_return_if_fail (THUNAR_IS_FILE (file));
1007
1008 /* unset the data */
1009 if (thunar_icon_factory_store_quark != 0)
1010 g_object_set_qdata (G_OBJECT (file), thunar_icon_factory_store_quark, NULL);
1011 }
1012