4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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)
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
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!) */
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
37 #include <sys/types.h>
46 #include "gui_support.h"
54 GFSCache
*pixmap_cache
= NULL
;
56 static const char * bad_xpm
[] = {
74 MaskedPixmap
*im_error
;
75 MaskedPixmap
*im_unknown
;
76 MaskedPixmap
*im_symlink
;
78 MaskedPixmap
*im_unmounted
;
79 MaskedPixmap
*im_mounted
;
80 MaskedPixmap
*im_appdir
;
82 MaskedPixmap
*im_dirs
;
84 typedef struct _ChildThumbnail ChildThumbnail
;
86 /* There is one of these for each active child process */
87 struct _ChildThumbnail
{
93 static const char *stocks
[] = {
94 ROX_STOCK_SHOW_DETAILS
,
95 ROX_STOCK_SHOW_HIDDEN
,
100 static GtkIconSize mount_icon_size
= -1;
102 /* Static prototypes */
104 static void load_default_pixmaps(void);
105 static gint
purge(gpointer data
);
106 static MaskedPixmap
*image_from_file(const char *path
);
107 static MaskedPixmap
*get_bad_image(void);
108 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
);
109 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
);
110 static GdkPixbuf
*get_thumbnail_for(const char *path
);
111 static void thumbnail_child_done(ChildThumbnail
*info
);
112 static void child_create_thumbnail(const gchar
*path
);
113 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
, guint32 color
,
115 static GList
*thumbs_purge_cache(Option
*option
, xmlNode
*node
, guchar
*label
);
117 /****************************************************************
118 * EXTERNAL INTERFACE *
119 ****************************************************************/
121 void pixmaps_init(void)
123 GtkIconFactory
*factory
;
126 gtk_widget_push_colormap(gdk_rgb_get_colormap());
128 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
, NULL
, NULL
);
130 gtk_timeout_add(10000, purge
, NULL
);
132 factory
= gtk_icon_factory_new();
133 for (i
= 0; i
< G_N_ELEMENTS(stocks
); i
++)
136 GError
*error
= NULL
;
139 const gchar
*name
= stocks
[i
];
141 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
142 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
145 g_warning("%s", error
->message
);
147 pixbuf
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
151 iset
= gtk_icon_set_new_from_pixbuf(pixbuf
);
152 g_object_unref(G_OBJECT(pixbuf
));
153 gtk_icon_factory_add(factory
, name
, iset
);
154 gtk_icon_set_unref(iset
);
156 gtk_icon_factory_add_default(factory
);
158 mount_icon_size
= gtk_icon_size_register("rox-mount-size", 14, 14);
160 load_default_pixmaps();
162 option_register_widget("thumbs-purge-cache", thumbs_purge_cache
);
165 /* Load image <appdir>/images/name.png.
166 * Always returns with a valid image.
168 MaskedPixmap
*load_pixmap(const char *name
)
171 MaskedPixmap
*retval
;
173 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
174 retval
= image_from_file(path
);
178 retval
= get_bad_image();
183 /* Create a MaskedPixmap from a GTK stock ID. Always returns
186 static MaskedPixmap
*mp_from_stock(const char *stock_id
, int size
)
188 GtkIconSet
*icon_set
;
190 MaskedPixmap
*retval
;
192 icon_set
= gtk_icon_factory_lookup_default(stock_id
);
194 return get_bad_image();
196 pixbuf
= gtk_icon_set_render_icon(icon_set
,
197 gtk_widget_get_default_style(), /* Gtk bug */
203 retval
= masked_pixmap_new(pixbuf
);
204 gdk_pixbuf_unref(pixbuf
);
209 void pixmap_make_huge(MaskedPixmap
*mp
)
214 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
216 /* Limit to small size now, otherwise they get scaled up in mixed mode.
219 mp
->huge_pixbuf
= scale_pixbuf_up(mp
->src_pixbuf
,
220 SMALL_WIDTH
, SMALL_HEIGHT
);
222 if (!mp
->huge_pixbuf
)
224 mp
->huge_pixbuf
= mp
->src_pixbuf
;
225 g_object_ref(mp
->huge_pixbuf
);
228 mp
->huge_pixbuf_lit
= create_spotlight_pixbuf(mp
->huge_pixbuf
,
230 mp
->huge_width
= gdk_pixbuf_get_width(mp
->huge_pixbuf
);
231 mp
->huge_height
= gdk_pixbuf_get_height(mp
->huge_pixbuf
);
234 void pixmap_make_small(MaskedPixmap
*mp
)
239 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
241 mp
->sm_pixbuf
= scale_pixbuf(mp
->src_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
245 mp
->sm_pixbuf
= mp
->src_pixbuf
;
246 g_object_ref(mp
->sm_pixbuf
);
249 mp
->sm_pixbuf_lit
= create_spotlight_pixbuf(mp
->sm_pixbuf
,
252 mp
->sm_width
= gdk_pixbuf_get_width(mp
->sm_pixbuf
);
253 mp
->sm_height
= gdk_pixbuf_get_height(mp
->sm_pixbuf
);
256 /* Load image 'path' in the background and insert into pixmap_cache.
257 * Call callback(data, path) when done (path is NULL => error).
258 * If the image is already uptodate, or being created already, calls the
259 * callback right away.
261 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
267 ChildThumbnail
*info
;
269 image
= g_fscache_lookup_full(pixmap_cache
, path
,
270 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
274 /* Thumbnail is known, or being created */
275 g_object_unref(image
);
276 callback(data
, NULL
);
280 g_return_if_fail(image
== NULL
);
282 pixbuf
= get_thumbnail_for(path
);
286 struct stat info1
, info2
;
289 dir
= g_path_get_dirname(path
);
291 /* If the image itself is in ~/.thumbnails, load it now
292 * (ie, don't create thumbnails for thumbnails!).
294 if (mc_stat(dir
, &info1
) != 0)
296 callback(data
, NULL
);
302 if (mc_stat(make_path(home_dir
, ".thumbnails/normal"),
304 info1
.st_dev
== info2
.st_dev
&&
305 info1
.st_ino
== info2
.st_ino
)
307 pixbuf
= gdk_pixbuf_new_from_file(path
, NULL
);
310 g_fscache_insert(pixmap_cache
,
312 callback(data
, NULL
);
322 image
= masked_pixmap_new(pixbuf
);
323 gdk_pixbuf_unref(pixbuf
);
324 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
325 callback(data
, (gchar
*) path
);
329 /* Add an entry, set to NULL, so no-one else tries to load this
332 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
338 delayed_error("fork(): %s", g_strerror(errno
));
339 callback(data
, NULL
);
345 /* We are the child process */
346 child_create_thumbnail(path
);
350 info
= g_new(ChildThumbnail
, 1);
351 info
->path
= g_strdup(path
);
352 info
->callback
= callback
;
354 on_child_death(child
, (CallbackFn
) thumbnail_child_done
, info
);
357 /****************************************************************
358 * INTERNAL FUNCTIONS *
359 ****************************************************************/
361 /* Create a thumbnail file for this image.
362 * XXX: Thumbnails should be deleted somewhere!
364 static void save_thumbnail(const char *pathname
, GdkPixbuf
*full
)
368 int original_width
, original_height
;
370 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
375 thumb
= scale_pixbuf(full
, 128, 128);
377 original_width
= gdk_pixbuf_get_width(full
);
378 original_height
= gdk_pixbuf_get_height(full
);
380 if (mc_stat(pathname
, &info
) != 0)
383 swidth
= g_strdup_printf("%d", original_width
);
384 sheight
= g_strdup_printf("%d", original_height
);
385 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
386 smtime
= g_strdup_printf("%ld", (long) info
.st_mtime
);
388 path
= pathdup(pathname
);
389 uri
= g_strconcat("file://", path
, NULL
);
393 to
= g_string_new(home_dir
);
394 g_string_append(to
, "/.thumbnails");
395 mkdir(to
->str
, 0700);
396 g_string_append(to
, "/normal/");
397 mkdir(to
->str
, 0700);
398 g_string_append(to
, md5
);
399 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
400 g_string_append_printf(to
, ".png.ROX-Filer-%ld", (long) getpid());
404 old_mask
= umask(0077);
405 gdk_pixbuf_save(thumb
, to
->str
, "png", NULL
,
406 "tEXt::Thumb::Image::Width", swidth
,
407 "tEXt::Thumb::Image::Height", sheight
,
408 "tEXt::Thumb::Size", ssize
,
409 "tEXt::Thumb::MTime", smtime
,
410 "tEXt::Thumb::URI", uri
,
411 "tEXt::Software", PROJECT
,
415 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
416 * a race condition if two programs create the same thumb at
422 final
= g_strndup(to
->str
, name_len
);
423 if (rename(to
->str
, final
))
424 g_warning("Failed to rename '%s' to '%s': %s",
425 to
->str
, final
, g_strerror(errno
));
429 g_string_free(to
, TRUE
);
437 /* Called in a subprocess. Load path and create the thumbnail
438 * file. Parent will notice when we die.
440 static void child_create_thumbnail(const gchar
*path
)
444 image
= gdk_pixbuf_new_from_file(path
, NULL
);
447 save_thumbnail(path
, image
);
449 /* (no need to unref, as we're about to exit) */
452 /* Called when the child process exits */
453 static void thumbnail_child_done(ChildThumbnail
*info
)
457 thumb
= get_thumbnail_for(info
->path
);
463 image
= masked_pixmap_new(thumb
);
464 g_object_unref(thumb
);
466 g_fscache_insert(pixmap_cache
, info
->path
, image
, FALSE
);
467 g_object_unref(image
);
469 info
->callback(info
->data
, info
->path
);
472 info
->callback(info
->data
, NULL
);
478 /* Check if we have an up-to-date thumbnail for this image.
479 * If so, return it. Otherwise, returns NULL.
481 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
483 GdkPixbuf
*thumb
= NULL
;
484 char *thumb_path
, *md5
, *uri
, *path
;
485 const char *ssize
, *smtime
;
488 path
= pathdup(pathname
);
489 uri
= g_strconcat("file://", path
, NULL
);
493 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
497 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
501 /* Note that these don't need freeing... */
502 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
506 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
510 if (mc_stat(path
, &info
) != 0)
513 if (info
.st_mtime
!= atol(smtime
) || info
.st_size
!= atol(ssize
))
519 gdk_pixbuf_unref(thumb
);
527 /* Load the image 'path' and return a pointer to the resulting
528 * MaskedPixmap. NULL on failure.
529 * Doesn't check for thumbnails (this is for small icons).
531 static MaskedPixmap
*image_from_file(const char *path
)
535 GError
*error
= NULL
;
537 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
540 g_warning("%s\n", error
->message
);
545 image
= masked_pixmap_new(pixbuf
);
547 gdk_pixbuf_unref(pixbuf
);
552 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
553 * If src is small enough, then ref it and return that.
555 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
559 w
= gdk_pixbuf_get_width(src
);
560 h
= gdk_pixbuf_get_height(src
);
562 if (w
<= max_w
&& h
<= max_h
)
569 float scale_x
= ((float) w
) / max_w
;
570 float scale_y
= ((float) h
) / max_h
;
571 float scale
= MAX(scale_x
, scale_y
);
572 int dest_w
= w
/ scale
;
573 int dest_h
= h
/ scale
;
575 return gdk_pixbuf_scale_simple(src
,
578 GDK_INTERP_BILINEAR
);
582 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
583 * If src is that size or bigger, then ref it and return that.
585 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
589 w
= gdk_pixbuf_get_width(src
);
590 h
= gdk_pixbuf_get_height(src
);
592 if (w
== 0 || h
== 0 || w
>= max_w
|| h
>= max_h
)
599 float scale_x
= max_w
/ ((float) w
);
600 float scale_y
= max_h
/ ((float) h
);
601 float scale
= MIN(scale_x
, scale_y
);
603 return gdk_pixbuf_scale_simple(src
,
606 GDK_INTERP_BILINEAR
);
610 /* Return a pointer to the (static) bad image. The ref counter will ensure
611 * that the image is never freed.
613 static MaskedPixmap
*get_bad_image(void)
618 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
619 mp
= masked_pixmap_new(bad
);
620 gdk_pixbuf_unref(bad
);
625 /* Called now and then to clear out old pixmaps */
626 static gint
purge(gpointer data
)
628 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
633 static gpointer parent_class
;
635 static void masked_pixmap_finialize(GObject
*object
)
637 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
641 g_object_unref(mp
->src_pixbuf
);
642 mp
->src_pixbuf
= NULL
;
647 g_object_unref(mp
->huge_pixbuf
);
648 mp
->huge_pixbuf
= NULL
;
650 if (mp
->huge_pixbuf_lit
)
652 g_object_unref(mp
->huge_pixbuf_lit
);
653 mp
->huge_pixbuf_lit
= NULL
;
658 g_object_unref(mp
->pixbuf
);
663 g_object_unref(mp
->pixbuf_lit
);
664 mp
->pixbuf_lit
= NULL
;
669 g_object_unref(mp
->sm_pixbuf
);
670 mp
->sm_pixbuf
= NULL
;
672 if (mp
->sm_pixbuf_lit
)
674 g_object_unref(mp
->sm_pixbuf_lit
);
675 mp
->sm_pixbuf_lit
= NULL
;
678 G_OBJECT_CLASS(parent_class
)->finalize(object
);
681 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
683 GObjectClass
*object
= (GObjectClass
*) gclass
;
685 parent_class
= g_type_class_peek_parent(gclass
);
687 object
->finalize
= masked_pixmap_finialize
;
690 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
692 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
694 mp
->src_pixbuf
= NULL
;
696 mp
->huge_pixbuf
= NULL
;
697 mp
->huge_pixbuf_lit
= NULL
;
699 mp
->huge_height
= -1;
702 mp
->pixbuf_lit
= NULL
;
706 mp
->sm_pixbuf
= NULL
;
707 mp
->sm_pixbuf_lit
= NULL
;
712 static GType
masked_pixmap_get_type(void)
714 static GType type
= 0;
718 static const GTypeInfo info
=
720 sizeof (MaskedPixmapClass
),
721 NULL
, /* base_init */
722 NULL
, /* base_finalise */
723 masked_pixmap_class_init
,
724 NULL
, /* class_finalise */
725 NULL
, /* class_data */
726 sizeof(MaskedPixmap
),
731 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
738 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
741 GdkPixbuf
*src_pixbuf
, *normal_pixbuf
;
743 g_return_val_if_fail(full_size
!= NULL
, NULL
);
745 src_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
746 g_return_val_if_fail(src_pixbuf
!= NULL
, NULL
);
748 normal_pixbuf
= scale_pixbuf(src_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
749 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
751 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
753 mp
->src_pixbuf
= src_pixbuf
;
755 mp
->pixbuf
= normal_pixbuf
;
756 mp
->pixbuf_lit
= create_spotlight_pixbuf(normal_pixbuf
, 0x000099, 128);
757 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
758 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
763 /* Stolen from eel...and modified to colourize the pixbuf.
764 * 'alpha' is the transparency of 'color' (0xRRGGBB):
765 * 0 = fully opaque, 255 = fully transparent.
767 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
,
773 int width
, height
, has_alpha
, src_row_stride
, dst_row_stride
;
774 guchar
*target_pixels
, *original_pixels
;
775 guchar
*pixsrc
, *pixdest
;
779 n_channels
= gdk_pixbuf_get_n_channels(src
);
780 has_alpha
= gdk_pixbuf_get_has_alpha(src
);
781 width
= gdk_pixbuf_get_width(src
);
782 height
= gdk_pixbuf_get_height(src
);
784 g_return_val_if_fail(gdk_pixbuf_get_colorspace(src
) ==
785 GDK_COLORSPACE_RGB
, NULL
);
786 g_return_val_if_fail((!has_alpha
&& n_channels
== 3) ||
787 (has_alpha
&& n_channels
== 4), NULL
);
788 g_return_val_if_fail(gdk_pixbuf_get_bits_per_sample(src
) == 8, NULL
);
790 dest
= gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src
), has_alpha
,
791 gdk_pixbuf_get_bits_per_sample(src
),
794 dst_row_stride
= gdk_pixbuf_get_rowstride(dest
);
795 src_row_stride
= gdk_pixbuf_get_rowstride(src
);
796 target_pixels
= gdk_pixbuf_get_pixels(dest
);
797 original_pixels
= gdk_pixbuf_get_pixels(src
);
799 r
= (color
& 0xff0000) >> 16;
800 g
= (color
& 0xff00) >> 8;
803 for (i
= 0; i
< height
; i
++)
807 pixdest
= target_pixels
+ i
* dst_row_stride
;
808 pixsrc
= original_pixels
+ i
* src_row_stride
;
809 for (j
= 0; j
< width
; j
++)
811 tmp
= (*pixsrc
++ * alpha
+ r
* (255 - alpha
)) / 255;
812 *pixdest
++ = (guchar
) MIN(255, tmp
);
813 tmp
= (*pixsrc
++ * alpha
+ g
* (255 - alpha
)) / 255;
814 *pixdest
++ = (guchar
) MIN(255, tmp
);
815 tmp
= (*pixsrc
++ * alpha
+ b
* (255 - alpha
)) / 255;
816 *pixdest
++ = (guchar
) MIN(255, tmp
);
818 *pixdest
++ = *pixsrc
++;
825 /* Load all the standard pixmaps. Also sets the default window icon. */
826 static void load_default_pixmaps(void)
829 GError
*error
= NULL
;
831 im_error
= mp_from_stock(GTK_STOCK_DIALOG_WARNING
,
832 GTK_ICON_SIZE_DIALOG
);
833 im_unknown
= mp_from_stock(GTK_STOCK_DIALOG_QUESTION
,
834 GTK_ICON_SIZE_DIALOG
);
835 im_symlink
= load_pixmap("symlink");
837 im_unmounted
= mp_from_stock(ROX_STOCK_MOUNT
, mount_icon_size
);
838 im_mounted
= mp_from_stock(ROX_STOCK_MOUNTED
, mount_icon_size
);
839 im_appdir
= load_pixmap("application");
841 im_dirs
= load_pixmap("dirs");
843 pixbuf
= gdk_pixbuf_new_from_file(
844 make_path(app_dir
, ".DirIcon"), &error
);
849 icon_list
= g_list_append(NULL
, pixbuf
);
850 gtk_window_set_default_icon_list(icon_list
);
851 g_list_free(icon_list
);
853 g_object_unref(G_OBJECT(pixbuf
));
857 g_warning("%s\n", error
->message
);
862 /* Also purges memory cache */
863 static void purge_disk_cache(GtkWidget
*button
, gpointer data
)
870 g_fscache_purge(pixmap_cache
, 0);
872 path
= g_strconcat(home_dir
, "/.thumbnails/normal/", NULL
);
877 report_error(_("Can't delete thumbnails in %s:\n%s"),
878 path
, g_strerror(errno
));
882 while ((ent
= readdir(dir
)))
884 if (ent
->d_name
[0] == '.')
886 list
= g_list_prepend(list
,
887 g_strconcat(path
, ent
->d_name
, NULL
));
895 destroy_glist(&list
);
898 info_message(_("There are no thumbnails to delete"));
903 static GList
*thumbs_purge_cache(Option
*option
, xmlNode
*node
, guchar
*label
)
905 GtkWidget
*button
, *align
;
907 g_return_val_if_fail(option
== NULL
, NULL
);
909 align
= gtk_alignment_new(0, 0.5, 0, 0);
910 button
= button_new_mixed(GTK_STOCK_CLEAR
,
911 _("Purge thumbnails disk cache"));
912 gtk_container_add(GTK_CONTAINER(align
), button
);
913 g_signal_connect(button
, "clicked", G_CALLBACK(purge_disk_cache
), NULL
);
915 return g_list_append(NULL
, align
);