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