r3874: Fix ref counting of thumbnails put into the cache
[rox-filer/dt.git] / ROX-Filer / src / pixmaps.c
blobbbf47da354a7f3ea94cadf144616aa8cf55839fc
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pixmaps.c - code for handling pixbufs (despite the name!) */
24 #include "config.h"
25 #define PIXMAPS_C
27 /* Remove pixmaps from the cache when they haven't been accessed for
28 * this period of time (seconds).
31 #define PIXMAP_PURGE_TIME 1200
32 #define PIXMAP_THUMB_SIZE 128
33 #define PIXMAP_THUMB_TOO_OLD_TIME 5
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <string.h>
43 #include <gtk/gtk.h>
45 #include "global.h"
47 #include "fscache.h"
48 #include "support.h"
49 #include "gui_support.h"
50 #include "pixmaps.h"
51 #include "main.h"
52 #include "filer.h"
53 #include "dir.h"
54 #include "diritem.h"
55 #include "choices.h"
56 #include "options.h"
57 #include "action.h"
58 #include "type.h"
60 GFSCache *pixmap_cache = NULL;
62 static const char * bad_xpm[] = {
63 "12 12 3 1",
64 " c #000000000000",
65 ". c #FFFF00000000",
66 "x c #FFFFFFFFFFFF",
67 " ",
68 " ..xxxxxx.. ",
69 " ...xxxx... ",
70 " x...xx...x ",
71 " xx......xx ",
72 " xxx....xxx ",
73 " xxx....xxx ",
74 " xx......xx ",
75 " x...xx...x ",
76 " ...xxxx... ",
77 " ..xxxxxx.. ",
78 " "};
80 MaskedPixmap *im_error;
81 MaskedPixmap *im_unknown;
82 MaskedPixmap *im_symlink;
84 MaskedPixmap *im_unmounted;
85 MaskedPixmap *im_mounted;
86 MaskedPixmap *im_appdir;
87 MaskedPixmap *im_xattr;
89 MaskedPixmap *im_dirs;
91 typedef struct _ChildThumbnail ChildThumbnail;
93 /* There is one of these for each active child process */
94 struct _ChildThumbnail {
95 gchar *path;
96 GFunc callback;
97 gpointer data;
100 static const char *stocks[] = {
101 ROX_STOCK_SHOW_DETAILS,
102 ROX_STOCK_SHOW_HIDDEN,
103 ROX_STOCK_SELECT,
104 ROX_STOCK_MOUNT,
105 ROX_STOCK_MOUNTED,
108 static GtkIconSize mount_icon_size = -1;
110 /* Static prototypes */
112 static void load_default_pixmaps(void);
113 static gint purge(gpointer data);
114 static MaskedPixmap *image_from_file(const char *path);
115 static MaskedPixmap *get_bad_image(void);
116 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
117 static GdkPixbuf *get_thumbnail_for(const char *path);
118 static void thumbnail_child_done(ChildThumbnail *info);
119 static void child_create_thumbnail(const gchar *path);
120 static GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src, guint32 color,
121 guchar alpha);
122 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label);
123 static gchar *thumbnail_path(const gchar *path);
124 static gchar *thumbnail_program(MIME_type *type);
126 /****************************************************************
127 * EXTERNAL INTERFACE *
128 ****************************************************************/
130 void pixmaps_init(void)
132 GtkIconFactory *factory;
133 int i;
135 gtk_widget_push_colormap(gdk_rgb_get_colormap());
137 pixmap_cache = g_fscache_new((GFSLoadFunc) image_from_file, NULL, NULL);
139 g_timeout_add(10000, purge, NULL);
141 factory = gtk_icon_factory_new();
142 for (i = 0; i < G_N_ELEMENTS(stocks); i++)
144 GdkPixbuf *pixbuf;
145 GError *error = NULL;
146 gchar *path;
147 GtkIconSet *iset;
148 const gchar *name = stocks[i];
150 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
151 pixbuf = gdk_pixbuf_new_from_file(path, &error);
152 if (!pixbuf)
154 g_warning("%s", error->message);
155 g_error_free(error);
156 pixbuf = gdk_pixbuf_new_from_xpm_data(bad_xpm);
158 g_free(path);
160 iset = gtk_icon_set_new_from_pixbuf(pixbuf);
161 g_object_unref(G_OBJECT(pixbuf));
162 gtk_icon_factory_add(factory, name, iset);
163 gtk_icon_set_unref(iset);
165 gtk_icon_factory_add_default(factory);
167 mount_icon_size = gtk_icon_size_register("rox-mount-size", 14, 14);
169 load_default_pixmaps();
171 option_register_widget("thumbs-purge-cache", thumbs_purge_cache);
174 /* Load image <appdir>/images/name.png.
175 * Always returns with a valid image.
177 MaskedPixmap *load_pixmap(const char *name)
179 guchar *path;
180 MaskedPixmap *retval;
182 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
183 retval = image_from_file(path);
184 g_free(path);
186 if (!retval)
187 retval = get_bad_image();
189 return retval;
192 /* Create a MaskedPixmap from a GTK stock ID. Always returns
193 * a valid image.
195 static MaskedPixmap *mp_from_stock(const char *stock_id, int size)
197 GtkIconSet *icon_set;
198 GdkPixbuf *pixbuf;
199 MaskedPixmap *retval;
201 icon_set = gtk_icon_factory_lookup_default(stock_id);
202 if (!icon_set)
203 return get_bad_image();
205 pixbuf = gtk_icon_set_render_icon(icon_set,
206 gtk_widget_get_default_style(), /* Gtk bug */
207 GTK_TEXT_DIR_LTR,
208 GTK_STATE_NORMAL,
209 size,
210 NULL,
211 NULL);
212 retval = masked_pixmap_new(pixbuf);
213 gdk_pixbuf_unref(pixbuf);
215 return retval;
218 void pixmap_make_huge(MaskedPixmap *mp)
220 if (mp->huge_pixbuf)
221 return;
223 g_return_if_fail(mp->src_pixbuf != NULL);
225 /* Limit to small size now, otherwise they get scaled up in mixed mode.
226 * Also looked ugly.
228 mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
229 SMALL_WIDTH, SMALL_HEIGHT);
231 if (!mp->huge_pixbuf)
233 mp->huge_pixbuf = mp->src_pixbuf;
234 g_object_ref(mp->huge_pixbuf);
237 mp->huge_pixbuf_lit = create_spotlight_pixbuf(mp->huge_pixbuf,
238 0x000099, 128);
239 mp->huge_width = gdk_pixbuf_get_width(mp->huge_pixbuf);
240 mp->huge_height = gdk_pixbuf_get_height(mp->huge_pixbuf);
243 void pixmap_make_small(MaskedPixmap *mp)
245 if (mp->sm_pixbuf)
246 return;
248 g_return_if_fail(mp->src_pixbuf != NULL);
250 mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
252 if (!mp->sm_pixbuf)
254 mp->sm_pixbuf = mp->src_pixbuf;
255 g_object_ref(mp->sm_pixbuf);
258 mp->sm_pixbuf_lit = create_spotlight_pixbuf(mp->sm_pixbuf,
259 0x000099, 128);
261 mp->sm_width = gdk_pixbuf_get_width(mp->sm_pixbuf);
262 mp->sm_height = gdk_pixbuf_get_height(mp->sm_pixbuf);
265 /* Load image 'path' in the background and insert into pixmap_cache.
266 * Call callback(data, path) when done (path is NULL => error).
267 * If the image is already uptodate, or being created already, calls the
268 * callback right away.
270 void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data)
272 gboolean found;
273 MaskedPixmap *image;
274 GdkPixbuf *pixbuf;
275 pid_t child;
276 ChildThumbnail *info;
277 MIME_type *type;
278 gchar *thumb_prog;
281 image = g_fscache_lookup_full(pixmap_cache, path,
282 FSCACHE_LOOKUP_ONLY_NEW, &found);
284 if (found)
286 /* Thumbnail is known, or being created */
287 if (image)
288 g_object_unref(image);
289 callback(data, NULL);
290 return;
293 g_return_if_fail(image == NULL);
295 pixbuf = get_thumbnail_for(path);
297 if (!pixbuf)
299 struct stat info1, info2;
300 char *dir;
302 dir = g_path_get_dirname(path);
304 /* If the image itself is in ~/.thumbnails, load it now
305 * (ie, don't create thumbnails for thumbnails!).
307 if (mc_stat(dir, &info1) != 0)
309 callback(data, NULL);
310 g_free(dir);
311 return;
313 g_free(dir);
315 if (mc_stat(make_path(home_dir, ".thumbnails/normal"),
316 &info2) == 0 &&
317 info1.st_dev == info2.st_dev &&
318 info1.st_ino == info2.st_ino)
320 pixbuf = rox_pixbuf_new_from_file_at_scale(path,
321 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
322 if (!pixbuf)
324 g_fscache_insert(pixmap_cache,
325 path, NULL, TRUE);
326 callback(data, NULL);
327 return;
332 if (pixbuf)
334 MaskedPixmap *image;
336 image = masked_pixmap_new(pixbuf);
337 gdk_pixbuf_unref(pixbuf);
338 g_fscache_insert(pixmap_cache, path, image, TRUE);
339 callback(data, (gchar *) path);
340 g_object_unref(G_OBJECT(image));
341 return;
344 type = type_from_path(path);
345 if (!type)
346 type = text_plain;
348 /* Add an entry, set to NULL, so no-one else tries to load this
349 * image.
351 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
353 thumb_prog = thumbnail_program(type);
355 /* Only attempt to load 'images' types ourselves */
356 if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0)
358 callback(data, NULL);
359 return; /* Don't know how to handle this type */
362 child = fork();
364 if (child == -1)
366 g_free(thumb_prog);
367 delayed_error("fork(): %s", g_strerror(errno));
368 callback(data, NULL);
369 return;
372 if (child == 0)
374 /* We are the child process. (We are sloppy with freeing
375 memory, but since we go away very quickly, that's ok.) */
376 if (thumb_prog)
378 DirItem *item;
380 item = diritem_new(g_basename(thumb_prog));
382 diritem_restat(thumb_prog, item, NULL);
383 if (item->flags & ITEM_FLAG_APPDIR)
384 thumb_prog = g_strconcat(thumb_prog, "/AppRun",
385 NULL);
387 execl(thumb_prog, thumb_prog, path,
388 thumbnail_path(path),
389 g_strdup_printf("%d", PIXMAP_THUMB_SIZE),
390 NULL);
391 _exit(1);
394 child_create_thumbnail(path);
395 _exit(0);
398 g_free(thumb_prog);
400 info = g_new(ChildThumbnail, 1);
401 info->path = g_strdup(path);
402 info->callback = callback;
403 info->data = data;
404 on_child_death(child, (CallbackFn) thumbnail_child_done, info);
407 /****************************************************************
408 * INTERNAL FUNCTIONS *
409 ****************************************************************/
411 /* Create a thumbnail file for this image */
412 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
414 struct stat info;
415 gchar *path;
416 int original_width, original_height;
417 GString *to;
418 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
419 mode_t old_mask;
420 int name_len;
421 GdkPixbuf *thumb;
423 thumb = scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
425 original_width = gdk_pixbuf_get_width(full);
426 original_height = gdk_pixbuf_get_height(full);
428 if (mc_stat(pathname, &info) != 0)
429 return;
431 swidth = g_strdup_printf("%d", original_width);
432 sheight = g_strdup_printf("%d", original_height);
433 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
434 smtime = g_strdup_printf("%ld", (long) info.st_mtime);
436 path = pathdup(pathname);
437 uri = g_filename_to_uri(path, NULL, NULL);
438 if (!uri)
439 uri = g_strconcat("file://", path, NULL);
440 md5 = md5_hash(uri);
441 g_free(path);
443 to = g_string_new(home_dir);
444 g_string_append(to, "/.thumbnails");
445 mkdir(to->str, 0700);
446 g_string_append(to, "/normal/");
447 mkdir(to->str, 0700);
448 g_string_append(to, md5);
449 name_len = to->len + 4; /* Truncate to this length when renaming */
450 g_string_append_printf(to, ".png.ROX-Filer-%ld", (long) getpid());
452 g_free(md5);
454 old_mask = umask(0077);
455 gdk_pixbuf_save(thumb, to->str, "png", NULL,
456 "tEXt::Thumb::Image::Width", swidth,
457 "tEXt::Thumb::Image::Height", sheight,
458 "tEXt::Thumb::Size", ssize,
459 "tEXt::Thumb::MTime", smtime,
460 "tEXt::Thumb::URI", uri,
461 "tEXt::Software", PROJECT,
462 NULL);
463 umask(old_mask);
465 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
466 * a race condition if two programs create the same thumb at
467 * once.
470 gchar *final;
472 final = g_strndup(to->str, name_len);
473 if (rename(to->str, final))
474 g_warning("Failed to rename '%s' to '%s': %s",
475 to->str, final, g_strerror(errno));
476 g_free(final);
479 g_string_free(to, TRUE);
480 g_free(swidth);
481 g_free(sheight);
482 g_free(ssize);
483 g_free(smtime);
484 g_free(uri);
487 static gchar *thumbnail_path(const char *path)
489 gchar *uri, *md5;
490 GString *to;
491 gchar *ans;
493 uri = g_filename_to_uri(path, NULL, NULL);
494 if(!uri)
495 uri = g_strconcat("file://", path, NULL);
496 md5 = md5_hash(uri);
498 to = g_string_new(home_dir);
499 g_string_append(to, "/.thumbnails");
500 mkdir(to->str, 0700);
501 g_string_append(to, "/normal/");
502 mkdir(to->str, 0700);
503 g_string_append(to, md5);
504 g_string_append(to, ".png");
506 g_free(md5);
507 g_free(uri);
509 ans=to->str;
510 g_string_free(to, FALSE);
512 return ans;
515 /* Return a program to create thumbnails for files of this type.
516 * NULL to try to make it ourself (using gdk).
517 * g_free the result.
519 static gchar *thumbnail_program(MIME_type *type)
521 gchar *leaf;
522 gchar *path;
524 if (!type)
525 return NULL;
527 leaf = g_strconcat(type->media_type, "_", type->subtype, NULL);
528 path = choices_find_path_load(leaf, "MIME-thumb");
529 g_free(leaf);
530 if (path)
532 return path;
535 path = choices_find_path_load(type->media_type, "MIME-thumb");
537 return path;
540 /* Called in a subprocess. Load path and create the thumbnail
541 * file. Parent will notice when we die.
543 static void child_create_thumbnail(const gchar *path)
545 GdkPixbuf *image;
547 image = rox_pixbuf_new_from_file_at_scale(path,
548 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
550 if (image)
551 save_thumbnail(path, image);
553 /* (no need to unref, as we're about to exit) */
556 /* Called when the child process exits */
557 static void thumbnail_child_done(ChildThumbnail *info)
559 GdkPixbuf *thumb;
561 thumb = get_thumbnail_for(info->path);
563 if (thumb)
565 MaskedPixmap *image;
567 image = masked_pixmap_new(thumb);
568 g_object_unref(thumb);
570 g_fscache_insert(pixmap_cache, info->path, image, FALSE);
571 g_object_unref(image);
573 info->callback(info->data, info->path);
575 else
576 info->callback(info->data, NULL);
578 g_free(info->path);
579 g_free(info);
582 /* Check if we have an up-to-date thumbnail for this image.
583 * If so, return it. Otherwise, returns NULL.
585 static GdkPixbuf *get_thumbnail_for(const char *pathname)
587 GdkPixbuf *thumb = NULL;
588 char *thumb_path, *md5, *uri, *path;
589 const char *ssize, *smtime;
590 struct stat info;
591 time_t ttime, now;
593 path = pathdup(pathname);
594 uri = g_filename_to_uri(path, NULL, NULL);
595 if(!uri)
596 uri = g_strconcat("file://", path, NULL);
597 md5 = md5_hash(uri);
598 g_free(uri);
600 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
601 home_dir, md5);
602 g_free(md5);
604 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
605 if (!thumb)
606 goto err;
608 /* Note that these don't need freeing... */
609 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
610 if (!ssize)
611 goto err;
613 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
614 if (!smtime)
615 goto err;
617 if (mc_stat(path, &info) != 0)
618 goto err;
620 ttime=(time_t) atol(smtime);
621 time(&now);
622 if (info.st_mtime != ttime && now>ttime+PIXMAP_THUMB_TOO_OLD_TIME)
623 goto err;
625 if (info.st_size < atol(ssize))
626 goto err;
628 goto out;
629 err:
630 if (thumb)
631 gdk_pixbuf_unref(thumb);
632 thumb = NULL;
633 out:
634 g_free(path);
635 g_free(thumb_path);
636 return thumb;
639 /* Load the image 'path' and return a pointer to the resulting
640 * MaskedPixmap. NULL on failure.
641 * Doesn't check for thumbnails (this is for small icons).
643 static MaskedPixmap *image_from_file(const char *path)
645 GdkPixbuf *pixbuf;
646 MaskedPixmap *image;
647 GError *error = NULL;
649 pixbuf = gdk_pixbuf_new_from_file(path, &error);
650 if (!pixbuf)
652 g_warning("%s\n", error->message);
653 g_error_free(error);
654 return NULL;
657 image = masked_pixmap_new(pixbuf);
659 gdk_pixbuf_unref(pixbuf);
661 return image;
664 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
665 * If src is small enough, then ref it and return that.
667 GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
669 int w, h;
671 w = gdk_pixbuf_get_width(src);
672 h = gdk_pixbuf_get_height(src);
674 if (w <= max_w && h <= max_h)
676 gdk_pixbuf_ref(src);
677 return src;
679 else
681 float scale_x = ((float) w) / max_w;
682 float scale_y = ((float) h) / max_h;
683 float scale = MAX(scale_x, scale_y);
684 int dest_w = w / scale;
685 int dest_h = h / scale;
687 return gdk_pixbuf_scale_simple(src,
688 MAX(dest_w, 1),
689 MAX(dest_h, 1),
690 GDK_INTERP_BILINEAR);
694 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
695 * If src is that size or bigger, then ref it and return that.
697 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
699 int w, h;
701 w = gdk_pixbuf_get_width(src);
702 h = gdk_pixbuf_get_height(src);
704 if (w == 0 || h == 0 || w >= max_w || h >= max_h)
706 gdk_pixbuf_ref(src);
707 return src;
709 else
711 float scale_x = max_w / ((float) w);
712 float scale_y = max_h / ((float) h);
713 float scale = MIN(scale_x, scale_y);
715 return gdk_pixbuf_scale_simple(src,
716 w * scale,
717 h * scale,
718 GDK_INTERP_BILINEAR);
722 /* Return a pointer to the (static) bad image. The ref counter will ensure
723 * that the image is never freed.
725 static MaskedPixmap *get_bad_image(void)
727 GdkPixbuf *bad;
728 MaskedPixmap *mp;
730 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
731 mp = masked_pixmap_new(bad);
732 gdk_pixbuf_unref(bad);
734 return mp;
737 /* Called now and then to clear out old pixmaps */
738 static gint purge(gpointer data)
740 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
742 return TRUE;
745 static gpointer parent_class;
747 static void masked_pixmap_finialize(GObject *object)
749 MaskedPixmap *mp = (MaskedPixmap *) object;
751 if (mp->src_pixbuf)
753 g_object_unref(mp->src_pixbuf);
754 mp->src_pixbuf = NULL;
757 if (mp->huge_pixbuf)
759 g_object_unref(mp->huge_pixbuf);
760 mp->huge_pixbuf = NULL;
762 if (mp->huge_pixbuf_lit)
764 g_object_unref(mp->huge_pixbuf_lit);
765 mp->huge_pixbuf_lit = NULL;
768 if (mp->pixbuf)
770 g_object_unref(mp->pixbuf);
771 mp->pixbuf = NULL;
773 if (mp->pixbuf_lit)
775 g_object_unref(mp->pixbuf_lit);
776 mp->pixbuf_lit = NULL;
779 if (mp->sm_pixbuf)
781 g_object_unref(mp->sm_pixbuf);
782 mp->sm_pixbuf = NULL;
784 if (mp->sm_pixbuf_lit)
786 g_object_unref(mp->sm_pixbuf_lit);
787 mp->sm_pixbuf_lit = NULL;
790 G_OBJECT_CLASS(parent_class)->finalize(object);
793 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
795 GObjectClass *object = (GObjectClass *) gclass;
797 parent_class = g_type_class_peek_parent(gclass);
799 object->finalize = masked_pixmap_finialize;
802 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
804 MaskedPixmap *mp = (MaskedPixmap *) object;
806 mp->src_pixbuf = NULL;
808 mp->huge_pixbuf = NULL;
809 mp->huge_pixbuf_lit = NULL;
810 mp->huge_width = -1;
811 mp->huge_height = -1;
813 mp->pixbuf = NULL;
814 mp->pixbuf_lit = NULL;
815 mp->width = -1;
816 mp->height = -1;
818 mp->sm_pixbuf = NULL;
819 mp->sm_pixbuf_lit = NULL;
820 mp->sm_width = -1;
821 mp->sm_height = -1;
824 static GType masked_pixmap_get_type(void)
826 static GType type = 0;
828 if (!type)
830 static const GTypeInfo info =
832 sizeof (MaskedPixmapClass),
833 NULL, /* base_init */
834 NULL, /* base_finalise */
835 masked_pixmap_class_init,
836 NULL, /* class_finalise */
837 NULL, /* class_data */
838 sizeof(MaskedPixmap),
839 0, /* n_preallocs */
840 masked_pixmap_init
843 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
844 &info, 0);
847 return type;
850 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
852 MaskedPixmap *mp;
853 GdkPixbuf *src_pixbuf, *normal_pixbuf;
855 g_return_val_if_fail(full_size != NULL, NULL);
857 src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
858 g_return_val_if_fail(src_pixbuf != NULL, NULL);
860 normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
861 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
863 mp = g_object_new(masked_pixmap_get_type(), NULL);
865 mp->src_pixbuf = src_pixbuf;
867 mp->pixbuf = normal_pixbuf;
868 mp->pixbuf_lit = create_spotlight_pixbuf(normal_pixbuf, 0x000099, 128);
869 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
870 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
872 return mp;
875 /* Stolen from eel...and modified to colourize the pixbuf.
876 * 'alpha' is the transparency of 'color' (0xRRGGBB):
877 * 0 = fully opaque, 255 = fully transparent.
879 static GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src,
880 guint32 color,
881 guchar alpha)
883 GdkPixbuf *dest;
884 int i, j;
885 int width, height, has_alpha, src_row_stride, dst_row_stride;
886 guchar *target_pixels, *original_pixels;
887 guchar *pixsrc, *pixdest;
888 guchar r, g, b;
889 gint n_channels;
891 n_channels = gdk_pixbuf_get_n_channels(src);
892 has_alpha = gdk_pixbuf_get_has_alpha(src);
893 width = gdk_pixbuf_get_width(src);
894 height = gdk_pixbuf_get_height(src);
896 g_return_val_if_fail(gdk_pixbuf_get_colorspace(src) ==
897 GDK_COLORSPACE_RGB, NULL);
898 g_return_val_if_fail((!has_alpha && n_channels == 3) ||
899 (has_alpha && n_channels == 4), NULL);
900 g_return_val_if_fail(gdk_pixbuf_get_bits_per_sample(src) == 8, NULL);
902 dest = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src), has_alpha,
903 gdk_pixbuf_get_bits_per_sample(src),
904 width, height);
906 dst_row_stride = gdk_pixbuf_get_rowstride(dest);
907 src_row_stride = gdk_pixbuf_get_rowstride(src);
908 target_pixels = gdk_pixbuf_get_pixels(dest);
909 original_pixels = gdk_pixbuf_get_pixels(src);
911 r = (color & 0xff0000) >> 16;
912 g = (color & 0xff00) >> 8;
913 b = color & 0xff;
915 for (i = 0; i < height; i++)
917 gint tmp;
919 pixdest = target_pixels + i * dst_row_stride;
920 pixsrc = original_pixels + i * src_row_stride;
921 for (j = 0; j < width; j++)
923 tmp = (*pixsrc++ * alpha + r * (255 - alpha)) / 255;
924 *pixdest++ = (guchar) MIN(255, tmp);
925 tmp = (*pixsrc++ * alpha + g * (255 - alpha)) / 255;
926 *pixdest++ = (guchar) MIN(255, tmp);
927 tmp = (*pixsrc++ * alpha + b * (255 - alpha)) / 255;
928 *pixdest++ = (guchar) MIN(255, tmp);
929 if (has_alpha)
930 *pixdest++ = *pixsrc++;
934 return dest;
937 /* Load all the standard pixmaps. Also sets the default window icon. */
938 static void load_default_pixmaps(void)
940 GdkPixbuf *pixbuf;
941 GError *error = NULL;
943 im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING,
944 GTK_ICON_SIZE_DIALOG);
945 im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION,
946 GTK_ICON_SIZE_DIALOG);
947 im_symlink = load_pixmap("symlink");
949 im_unmounted = mp_from_stock(ROX_STOCK_MOUNT, mount_icon_size);
950 im_mounted = mp_from_stock(ROX_STOCK_MOUNTED, mount_icon_size);
951 im_appdir = load_pixmap("application");
952 im_xattr = load_pixmap("rox-xattr");
954 im_dirs = load_pixmap("dirs");
956 pixbuf = gdk_pixbuf_new_from_file(
957 make_path(app_dir, ".DirIcon"), &error);
958 if (pixbuf)
960 GList *icon_list;
962 icon_list = g_list_append(NULL, pixbuf);
963 gtk_window_set_default_icon_list(icon_list);
964 g_list_free(icon_list);
966 g_object_unref(G_OBJECT(pixbuf));
968 else
970 g_warning("%s\n", error->message);
971 g_error_free(error);
975 /* Also purges memory cache */
976 static void purge_disk_cache(GtkWidget *button, gpointer data)
978 char *path;
979 GList *list = NULL;
980 DIR *dir;
981 struct dirent *ent;
983 g_fscache_purge(pixmap_cache, 0);
985 path = g_strconcat(home_dir, "/.thumbnails/normal/", NULL);
987 dir = opendir(path);
988 if (!dir)
990 report_error(_("Can't delete thumbnails in %s:\n%s"),
991 path, g_strerror(errno));
992 goto out;
995 while ((ent = readdir(dir)))
997 if (ent->d_name[0] == '.')
998 continue;
999 list = g_list_prepend(list,
1000 g_strconcat(path, ent->d_name, NULL));
1003 closedir(dir);
1005 if (list)
1007 action_delete(list);
1008 destroy_glist(&list);
1010 else
1011 info_message(_("There are no thumbnails to delete"));
1012 out:
1013 g_free(path);
1016 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label)
1018 GtkWidget *button, *align;
1020 g_return_val_if_fail(option == NULL, NULL);
1022 align = gtk_alignment_new(0, 0.5, 0, 0);
1023 button = button_new_mixed(GTK_STOCK_CLEAR,
1024 _("Purge thumbnails disk cache"));
1025 gtk_container_add(GTK_CONTAINER(align), button);
1026 g_signal_connect(button, "clicked", G_CALLBACK(purge_disk_cache), NULL);
1028 return g_list_append(NULL, align);