4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 pixmaps */
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>
41 #include <gdk-pixbuf/gdk-pixbuf.h>
42 #include <gdk-pixbuf/gdk-pixbuf-loader.h>
48 #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_multiple
;
81 MaskedPixmap
*im_appdir
;
83 MaskedPixmap
*im_help
;
84 MaskedPixmap
*im_dirs
;
86 /* Static prototypes */
88 static void load_default_pixmaps(void);
89 static gint
purge(gpointer data
);
90 static MaskedPixmap
*image_from_file(const char *path
);
91 static MaskedPixmap
*get_bad_image(void);
92 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
);
93 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
);
94 static void got_thumb_data(GdkPixbufLoader
*loader
,
95 gint fd
, GdkInputCondition cond
);
96 static GdkPixbuf
*get_thumbnail_for(const char *path
);
99 /****************************************************************
100 * EXTERNAL INTERFACE *
101 ****************************************************************/
103 void pixmaps_init(void)
105 gtk_widget_push_colormap(gdk_rgb_get_cmap());
107 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
,
108 (GFSRefFunc
) g_object_ref
,
109 (GFSRefFunc
) g_object_unref
,
110 (GFSGetRefFunc
) object_getref
,
111 NULL
, /* Update func */
114 gtk_timeout_add(10000, purge
, NULL
);
116 load_default_pixmaps();
119 /* 'name' is relative to app_dir. Always returns with a valid image. */
120 MaskedPixmap
*load_pixmap(const char *name
)
122 MaskedPixmap
*retval
;
124 retval
= image_from_file(make_path(app_dir
, name
)->str
);
126 retval
= get_bad_image();
130 /* Load all the standard pixmaps */
131 static void load_default_pixmaps(void)
133 im_error
= load_pixmap("pixmaps/error.xpm");
134 im_unknown
= load_pixmap("pixmaps/unknown.xpm");
135 im_symlink
= load_pixmap("pixmaps/symlink.xpm");
137 im_unmounted
= load_pixmap("pixmaps/mount.xpm");
138 im_mounted
= load_pixmap("pixmaps/mounted.xpm");
139 im_multiple
= load_pixmap("pixmaps/multiple.xpm");
140 im_appdir
= load_pixmap("pixmaps/application.xpm");
142 im_help
= load_pixmap("pixmaps/help.xpm");
143 im_dirs
= load_pixmap("pixmaps/dirs.xpm");
146 void pixmap_make_huge(MaskedPixmap
*mp
)
153 g_return_if_fail(mp
->huge_pixbuf
!= NULL
);
155 hg
= scale_pixbuf_up(mp
->huge_pixbuf
,
161 gdk_pixbuf_render_pixmap_and_mask(hg
,
165 mp
->huge_width
= gdk_pixbuf_get_width(hg
);
166 mp
->huge_height
= gdk_pixbuf_get_height(hg
);
167 gdk_pixbuf_unref(hg
);
170 if (!mp
->huge_pixmap
)
172 gdk_pixmap_ref(mp
->pixmap
);
174 gdk_bitmap_ref(mp
->mask
);
175 mp
->huge_pixmap
= mp
->pixmap
;
176 mp
->huge_mask
= mp
->mask
;
177 mp
->huge_width
= mp
->width
;
178 mp
->huge_height
= mp
->height
;
182 void pixmap_make_small(MaskedPixmap
*mp
)
189 g_return_if_fail(mp
->huge_pixbuf
!= NULL
);
191 sm
= scale_pixbuf(mp
->huge_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
195 gdk_pixbuf_render_pixmap_and_mask(sm
,
199 mp
->sm_width
= gdk_pixbuf_get_width(sm
);
200 mp
->sm_height
= gdk_pixbuf_get_height(sm
);
201 gdk_pixbuf_unref(sm
);
207 gdk_pixmap_ref(mp
->pixmap
);
209 gdk_bitmap_ref(mp
->mask
);
210 mp
->sm_pixmap
= mp
->pixmap
;
211 mp
->sm_mask
= mp
->mask
;
212 mp
->sm_width
= mp
->width
;
213 mp
->sm_height
= mp
->height
;
216 /* XXX: These don't need to be macros anymore */
217 #define GET_LOADER(key) (g_object_get_data(G_OBJECT(loader), key))
218 #define SET_LOADER(key, data) (g_object_set_data(G_OBJECT(loader), key, data))
219 #define SET_LOADER_FULL(key, data, free) \
220 (g_object_set_data_full(G_OBJECT(loader), key, data, free))
222 /* Load image 'path' in the background and insert into pixmap_cache.
223 * Call callback(data, path) when done (path is NULL => error).
224 * If the image is already uptodate, or being created already, calls the
225 * callback right away.
227 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
229 GdkPixbufLoader
*loader
;
235 image
= g_fscache_lookup_full(pixmap_cache
, path
,
236 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
240 /* Thumbnail is known, or being created */
241 g_fscache_data_unref(pixmap_cache
, image
);
242 callback(data
, NULL
);
246 g_return_if_fail(image
== NULL
);
248 pixbuf
= get_thumbnail_for(path
);
253 image
= masked_pixmap_new(pixbuf
);
254 gdk_pixbuf_unref(pixbuf
);
255 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
256 callback(data
, (gchar
*) path
);
260 /* Add an entry, set to NULL, so no-one else tries to load this
263 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
265 fd
= mc_open(path
, O_RDONLY
| O_NONBLOCK
);
268 callback(data
, NULL
);
272 loader
= gdk_pixbuf_loader_new();
274 SET_LOADER_FULL("thumb-path", g_strdup(path
), g_free
);
275 SET_LOADER("thumb-callback", callback
);
276 SET_LOADER("thumb-callback-data", data
);
278 tag
= gdk_input_add(fd
, GDK_INPUT_READ
,
279 (GdkInputFunction
) got_thumb_data
, loader
);
281 SET_LOADER("thumb-input-tag", GINT_TO_POINTER(tag
));
284 /****************************************************************
285 * INTERNAL FUNCTIONS *
286 ****************************************************************/
288 /* Create a thumbnail file for this image.
289 * XXX: Thumbnails should be deleted somewhere!
291 static void save_thumbnail(char *path
, GdkPixbuf
*full
, MaskedPixmap
*image
)
294 int original_width
, original_height
;
296 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
300 /* If the source image was very small, don't bother saving */
301 if (gdk_pixbuf_get_width(full
) * gdk_pixbuf_get_height(full
) <
302 (HUGE_WIDTH
* HUGE_HEIGHT
* 3))
305 original_width
= gdk_pixbuf_get_width(full
);
306 original_height
= gdk_pixbuf_get_height(full
);
308 if (mc_stat(path
, &info
) != 0)
311 swidth
= g_strdup_printf("%d", original_width
);
312 sheight
= g_strdup_printf("%d", original_height
);
313 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
314 smtime
= g_strdup_printf("%ld", info
.st_mtime
);
316 path
= pathdup(path
);
317 uri
= g_strconcat("file://", path
, NULL
);
321 to
= g_string_new(home_dir
);
322 g_string_append(to
, "/.thumbnails");
323 mkdir(to
->str
, 0700);
324 g_string_append(to
, "/normal/");
325 mkdir(to
->str
, 0700);
326 g_string_append(to
, md5
);
327 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
328 g_string_sprintfa(to
, ".png.ROX-Filer-%ld", (long) getpid());
332 old_mask
= umask(0077);
333 gdk_pixbuf_save(image
->huge_pixbuf
,
337 "tEXt::Thumb::Image::Width", swidth
,
338 "tEXt::Thumb::Image::Height", sheight
,
339 "tEXt::Thumb::Size", ssize
,
340 "tEXt::Thumb::MTime", smtime
,
341 "tEXt::Thumb::URI", uri
,
342 "tEXt::Software", PROJECT
,
346 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
347 * a race condition if two program create the same thumb at
353 final
= g_strndup(to
->str
, name_len
);
354 if (rename(to
->str
, final
))
355 g_warning("Failed to rename '%s' to '%s': %s",
356 to
->str
, final
, g_strerror(errno
));
360 g_string_free(to
, TRUE
);
368 /* Check if we have an up-to-date thumbnail for this image.
369 * If so, return it. Otherwise, returns NULL.
371 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
373 GdkPixbuf
*thumb
= NULL
;
374 char *thumb_path
, *md5
, *uri
, *path
;
375 const char *ssize
, *smtime
;
378 path
= pathdup(pathname
);
379 uri
= g_strconcat("file://", path
, NULL
);
383 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
387 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
391 /* Note that these don't need freeing... */
392 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
396 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
400 if (mc_stat(path
, &info
) != 0)
403 if (info
.st_mtime
!= atol(smtime
) || info
.st_size
!= atol(ssize
))
409 gdk_pixbuf_unref(thumb
);
417 /* Load the image 'path' and return a pointer to the resulting
418 * MaskedPixmap. NULL on failure.
419 * Doesn't check for thumbnails (this is for small icons).
421 static MaskedPixmap
*image_from_file(const char *path
)
425 GError
*error
= NULL
;
427 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
430 g_print("%s\n", error
? error
->message
: _("Unknown error"));
435 image
= masked_pixmap_new(pixbuf
);
437 gdk_pixbuf_unref(pixbuf
);
442 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
443 * If src is small enough, then ref it and return that.
445 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
449 w
= gdk_pixbuf_get_width(src
);
450 h
= gdk_pixbuf_get_height(src
);
452 if (w
<= max_w
&& h
<= max_h
)
459 float scale_x
= ((float) w
) / max_w
;
460 float scale_y
= ((float) h
) / max_h
;
461 float scale
= MAX(scale_x
, scale_y
);
462 int dest_w
= w
/ scale
;
463 int dest_h
= h
/ scale
;
465 return gdk_pixbuf_scale_simple(src
,
468 GDK_INTERP_BILINEAR
);
472 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
473 * If src is that size or bigger, then ref it and return that.
475 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
479 w
= gdk_pixbuf_get_width(src
);
480 h
= gdk_pixbuf_get_height(src
);
482 if (w
== 0 || h
== 0 || (w
>= max_w
&& h
>= max_h
))
489 float scale_x
= max_w
/ ((float) w
);
490 float scale_y
= max_h
/ ((float) h
);
491 float scale
= MIN(scale_x
, scale_y
);
493 return gdk_pixbuf_scale_simple(src
,
496 GDK_INTERP_BILINEAR
);
500 /* Return a pointer to the (static) bad image. The ref counter will ensure
501 * that the image is never freed.
503 static MaskedPixmap
*get_bad_image(void)
508 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
509 mp
= masked_pixmap_new(bad
);
510 gdk_pixbuf_unref(bad
);
515 /* Called now and then to clear out old pixmaps */
516 static gint
purge(gpointer data
)
518 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
523 static void got_thumb_data(GdkPixbufLoader
*loader
,
524 gint fd
, GdkInputCondition cond
)
532 got
= read(fd
, data
, sizeof(data
));
534 if (got
> 0 && gdk_pixbuf_loader_write(loader
, data
, got
, NULL
))
537 /* Some kind of error, or end-of-file */
539 path
= GET_LOADER("thumb-path");
541 tag
= GPOINTER_TO_INT(GET_LOADER("thumb-input-tag"));
542 gdk_input_remove(tag
);
544 gdk_pixbuf_loader_close(loader
, NULL
);
551 pixbuf
= gdk_pixbuf_loader_get_pixbuf(loader
);
554 gdk_pixbuf_ref(pixbuf
);
555 image
= masked_pixmap_new(pixbuf
);
557 g_fscache_insert(pixmap_cache
, path
, image
, FALSE
);
560 save_thumbnail(path
, pixbuf
, image
);
561 g_object_unref(image
);
563 gdk_pixbuf_unref(pixbuf
);
571 callback
= GET_LOADER("thumb-callback");
572 cbdata
= GET_LOADER("thumb-callback-data");
574 callback(cbdata
, got
!= 0 ? NULL
: path
);
576 g_object_unref(G_OBJECT(loader
));
579 static gpointer parent_class
;
581 static void masked_pixmap_finialize(GObject
*object
)
583 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
585 g_print("[ finalize ]\n");
589 gdk_pixbuf_unref(mp
->huge_pixbuf
);
590 mp
->huge_pixbuf
= NULL
;
595 gdk_pixmap_unref(mp
->huge_pixmap
);
596 mp
->huge_pixmap
= NULL
;
600 gdk_bitmap_unref(mp
->huge_mask
);
601 mp
->huge_mask
= NULL
;
606 g_object_unref(mp
->pixmap
);
611 g_object_unref(mp
->mask
);
617 g_object_unref(mp
->sm_pixmap
);
618 mp
->sm_pixmap
= NULL
;
622 g_object_unref(mp
->sm_mask
);
626 G_OBJECT_CLASS(parent_class
)->finalize(object
);
629 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
631 GObjectClass
*object
= (GObjectClass
*) gclass
;
633 parent_class
= g_type_class_peek_parent(gclass
);
635 object
->finalize
= masked_pixmap_finialize
;
638 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
640 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
642 mp
->huge_pixbuf
= NULL
;
644 mp
->huge_pixmap
= NULL
;
645 mp
->huge_mask
= NULL
;
647 mp
->huge_height
= -1;
654 mp
->sm_pixmap
= NULL
;
660 static GType
masked_pixmap_get_type(void)
662 static GType type
= 0;
666 static const GTypeInfo info
=
668 sizeof (MaskedPixmapClass
),
669 NULL
, /* base_init */
670 NULL
, /* base_finalise */
671 masked_pixmap_class_init
,
672 NULL
, /* class_finalise */
673 NULL
, /* class_data */
674 sizeof(MaskedPixmap
),
679 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
686 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
689 GdkPixbuf
*huge_pixbuf
, *normal_pixbuf
;
693 g_return_val_if_fail(full_size
!= NULL
, NULL
);
695 huge_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
696 g_return_val_if_fail(huge_pixbuf
!= NULL
, NULL
);
698 normal_pixbuf
= scale_pixbuf(huge_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
699 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
701 gdk_pixbuf_render_pixmap_and_mask(normal_pixbuf
, &pixmap
, &mask
, 128);
705 gdk_pixbuf_unref(huge_pixbuf
);
706 gdk_pixbuf_unref(normal_pixbuf
);
710 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
712 mp
->huge_pixbuf
= huge_pixbuf
;
716 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
717 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
719 gdk_pixbuf_unref(normal_pixbuf
);