4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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
;
55 FilerWindow
*pixmap_cache_load_via
= NULL
;
57 static const char * bad_xpm
[] = {
75 MaskedPixmap
*im_error
;
76 MaskedPixmap
*im_unknown
;
77 MaskedPixmap
*im_symlink
;
79 MaskedPixmap
*im_unmounted
;
80 MaskedPixmap
*im_mounted
;
81 MaskedPixmap
*im_multiple
;
82 MaskedPixmap
*im_appdir
;
84 MaskedPixmap
*im_help
;
85 MaskedPixmap
*im_dirs
;
87 /* Static prototypes */
89 static void load_default_pixmaps(void);
90 static MaskedPixmap
*load(char *pathname
, gpointer data
);
91 static void ref(MaskedPixmap
*mp
, gpointer data
);
92 static void unref(MaskedPixmap
*mp
, gpointer data
);
93 static int getref(MaskedPixmap
*mp
);
94 static gint
purge(gpointer data
);
95 static MaskedPixmap
*image_from_file(char *path
);
96 static MaskedPixmap
*get_bad_image(void);
97 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
);
98 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
);
99 static void got_thumb_data(GdkPixbufLoader
*loader
,
100 gint fd
, GdkInputCondition cond
);
103 /****************************************************************
104 * EXTERNAL INTERFACE *
105 ****************************************************************/
107 void pixmaps_init(void)
111 gtk_widget_push_visual(gdk_rgb_get_visual());
113 gtk_widget_push_colormap(gdk_rgb_get_cmap());
115 pixmap_cache
= g_fscache_new((GFSLoadFunc
) load
,
118 (GFSGetRefFunc
) getref
,
119 NULL
, /* Update func */
122 gtk_timeout_add(10000, purge
, NULL
);
124 load_default_pixmaps();
127 /* 'name' is relative to app_dir. Always returns with a valid image. */
128 MaskedPixmap
*load_pixmap(char *name
)
130 MaskedPixmap
*retval
;
132 retval
= image_from_file(make_path(app_dir
, name
)->str
);
134 retval
= get_bad_image();
138 /* Load all the standard pixmaps */
139 static void load_default_pixmaps(void)
141 im_error
= load_pixmap("pixmaps/error.xpm");
142 im_unknown
= load_pixmap("pixmaps/unknown.xpm");
143 im_symlink
= load_pixmap("pixmaps/symlink.xpm");
145 im_unmounted
= load_pixmap("pixmaps/mount.xpm");
146 im_mounted
= load_pixmap("pixmaps/mounted.xpm");
147 im_multiple
= load_pixmap("pixmaps/multiple.xpm");
148 im_appdir
= load_pixmap("pixmaps/application.xpm");
150 im_help
= load_pixmap("pixmaps/help.xpm");
151 im_dirs
= load_pixmap("pixmaps/dirs.xpm");
154 void pixmap_ref(MaskedPixmap
*mp
)
159 void pixmap_unref(MaskedPixmap
*mp
)
164 void pixmap_make_huge(MaskedPixmap
*mp
)
171 g_return_if_fail(mp
->huge_pixbuf
!= NULL
);
173 hg
= scale_pixbuf_up(mp
->huge_pixbuf
,
179 gdk_pixbuf_render_pixmap_and_mask(hg
,
183 mp
->huge_width
= gdk_pixbuf_get_width(hg
);
184 mp
->huge_height
= gdk_pixbuf_get_height(hg
);
185 gdk_pixbuf_unref(hg
);
188 if (!mp
->huge_pixmap
)
190 gdk_pixmap_ref(mp
->pixmap
);
192 gdk_bitmap_ref(mp
->mask
);
193 mp
->huge_pixmap
= mp
->pixmap
;
194 mp
->huge_mask
= mp
->mask
;
195 mp
->huge_width
= mp
->width
;
196 mp
->huge_height
= mp
->height
;
200 void pixmap_make_small(MaskedPixmap
*mp
)
207 g_return_if_fail(mp
->huge_pixbuf
!= NULL
);
209 sm
= scale_pixbuf(mp
->huge_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
213 gdk_pixbuf_render_pixmap_and_mask(sm
,
217 mp
->sm_width
= gdk_pixbuf_get_width(sm
);
218 mp
->sm_height
= gdk_pixbuf_get_height(sm
);
219 gdk_pixbuf_unref(sm
);
225 gdk_pixmap_ref(mp
->pixmap
);
227 gdk_bitmap_ref(mp
->mask
);
228 mp
->sm_pixmap
= mp
->pixmap
;
229 mp
->sm_mask
= mp
->mask
;
230 mp
->sm_width
= mp
->width
;
231 mp
->sm_height
= mp
->height
;
235 # define GET_LOADER(key) (g_object_get_data(G_OBJECT(loader), key))
236 # define SET_LOADER(key, data) (g_object_set_data(G_OBJECT(loader), key, data))
237 # define SET_LOADER_FULL(key, data, free) \
238 (g_object_set_data_full(G_OBJECT(loader), key, data, free))
240 # define GET_LOADER(key) (gtk_object_get_data(GTK_OBJECT(loader), key))
241 # define SET_LOADER(key, data) \
242 (gtk_object_set_data(GTK_OBJECT(loader), key, data))
243 # define SET_LOADER_FULL(key, data, free) \
244 (gtk_object_set_data_full(GTK_OBJECT(loader), key, data, free))
247 /* Load image 'path' in the background and insert into pixmap_cache.
248 * Call callback(data, path) when done (PATH is NULL error).
250 void pixmap_background_thumb(gchar
*path
, GFunc callback
, gpointer data
)
252 GdkPixbufLoader
*loader
;
255 fd
= mc_open(path
, O_RDONLY
| O_NONBLOCK
);
258 callback(data
, NULL
);
262 loader
= gdk_pixbuf_loader_new();
264 SET_LOADER_FULL("thumb-path", g_strdup(path
), g_free
);
265 SET_LOADER("thumb-callback", callback
);
266 SET_LOADER("thumb-callback-data", data
);
268 tag
= gdk_input_add(fd
, GDK_INPUT_READ
,
269 (GdkInputFunction
) got_thumb_data
, loader
);
271 SET_LOADER("thumb-input-tag", GINT_TO_POINTER(tag
));
274 /****************************************************************
275 * INTERNAL FUNCTIONS *
276 ****************************************************************/
279 /* Create a thumbnail file for this image.
280 * XXX: Thumbnails should be deleted somewhere!
282 static void save_thumbnail(char *path
, GdkPixbuf
*full
, MaskedPixmap
*image
)
285 int original_width
, original_height
;
287 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
290 original_width
= gdk_pixbuf_get_width(full
);
291 original_height
= gdk_pixbuf_get_height(full
);
293 if (mc_stat(path
, &info
) != 0)
296 swidth
= g_strdup_printf("%d", original_width
);
297 sheight
= g_strdup_printf("%d", original_height
);
298 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
299 smtime
= g_strdup_printf("%ld", info
.st_mtime
);
301 path
= pathdup(path
);
302 uri
= g_strconcat("file://", path
, NULL
);
306 to
= g_string_new(home_dir
);
307 g_string_append(to
, "/.thumbnails");
308 mkdir(to
->str
, 0700);
309 g_string_append(to
, "/96x96/");
310 mkdir(to
->str
, 0700);
311 g_string_append(to
, md5
);
312 g_string_append(to
, ".png");
316 old_mask
= umask(0077);
317 gdk_pixbuf_save(image
->huge_pixbuf
,
321 "tEXt::Thumb::Image::Width", swidth
,
322 "tEXt::Thumb::Image::Height", sheight
,
323 "tEXt::Thumb::Size", ssize
,
324 "tEXt::Thumb::MTime", smtime
,
325 "tEXt::Thumb::URI", uri
,
326 "tEXt::Software", PROJECT
,
330 g_string_free(to
, TRUE
);
338 /* Check if we have an up-to-date thumbnail for this image.
339 * If so, return it. Otherwise, returns NULL.
341 static GdkPixbuf
*get_thumbnail_for(char *path
)
344 char *thumb_path
, *md5
, *uri
;
345 char *ssize
, *smtime
;
348 path
= pathdup(path
);
349 uri
= g_strconcat("file://", path
, NULL
);
353 thumb_path
= g_strdup_printf("%s/.thumbnails/96x96/%s.png",
357 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
361 /* Note that these don't need freeing... */
362 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
366 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
370 if (mc_stat(path
, &info
) != 0)
373 if (info
.st_mtime
!= atol(smtime
) || info
.st_size
!= atol(ssize
))
379 gdk_pixbuf_unref(thumb
);
390 /* Load the image 'path' and return a pointer to the resulting
391 * MaskedPixmap. NULL on failure.
393 static MaskedPixmap
*image_from_file(char *path
)
398 GError
*error
= NULL
;
400 pixbuf
= get_thumbnail_for(path
);
402 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
405 g_print("%s\n", error
? error
->message
: _("Unknown error"));
410 pixbuf
= gdk_pixbuf_new_from_file(path
);
415 image
= image_from_pixbuf(pixbuf
);
418 /* If the source image was very large, save a thumbnail */
419 if (gdk_pixbuf_get_width(pixbuf
) * gdk_pixbuf_get_height(pixbuf
) >
420 (HUGE_WIDTH
* HUGE_HEIGHT
* 3))
421 save_thumbnail(path
, pixbuf
, image
);
424 gdk_pixbuf_unref(pixbuf
);
429 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
430 * If src is small enough, then ref it and return that.
432 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
436 w
= gdk_pixbuf_get_width(src
);
437 h
= gdk_pixbuf_get_height(src
);
439 if (w
<= max_w
&& h
<= max_h
)
446 float scale_x
= ((float) w
) / max_w
;
447 float scale_y
= ((float) h
) / max_h
;
448 float scale
= MAX(scale_x
, scale_y
);
449 int dest_w
= w
/ scale
;
450 int dest_h
= h
/ scale
;
452 return gdk_pixbuf_scale_simple(src
,
455 GDK_INTERP_BILINEAR
);
459 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
460 * If src is that size or bigger, then ref it and return that.
462 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
466 w
= gdk_pixbuf_get_width(src
);
467 h
= gdk_pixbuf_get_height(src
);
469 if (w
== 0 || h
== 0 || (w
>= max_w
&& h
>= max_h
))
476 float scale_x
= max_w
/ ((float) w
);
477 float scale_y
= max_h
/ ((float) h
);
478 float scale
= MIN(scale_x
, scale_y
);
480 return gdk_pixbuf_scale_simple(src
,
483 GDK_INTERP_BILINEAR
);
487 /* Turn a full-size pixbuf into a MaskedPixmap */
488 MaskedPixmap
*image_from_pixbuf(GdkPixbuf
*full_size
)
491 GdkPixbuf
*huge_pixbuf
, *normal_pixbuf
;
495 g_return_val_if_fail(full_size
!= NULL
, NULL
);
497 huge_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
498 g_return_val_if_fail(huge_pixbuf
!= NULL
, NULL
);
500 normal_pixbuf
= scale_pixbuf(huge_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
501 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
503 gdk_pixbuf_render_pixmap_and_mask(normal_pixbuf
, &pixmap
, &mask
, 128);
507 gdk_pixbuf_unref(huge_pixbuf
);
508 gdk_pixbuf_unref(normal_pixbuf
);
512 mp
= g_new(MaskedPixmap
, 1);
513 mp
->huge_pixbuf
= huge_pixbuf
;
518 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
519 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
521 gdk_pixbuf_unref(normal_pixbuf
);
523 mp
->huge_pixmap
= NULL
;
524 mp
->huge_mask
= NULL
;
526 mp
->huge_height
= -1;
528 mp
->sm_pixmap
= NULL
;
536 /* Return a pointer to the (static) bad image. The ref counter will ensure
537 * that the image is never freed.
539 static MaskedPixmap
*get_bad_image(void)
544 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
545 mp
= image_from_pixbuf(bad
);
546 gdk_pixbuf_unref(bad
);
551 static MaskedPixmap
*load(char *pathname
, gpointer user_data
)
553 if (pixmap_cache_load_via
)
555 filer_create_thumb(pixmap_cache_load_via
, pathname
);
559 return image_from_file(pathname
);
562 static void ref(MaskedPixmap
*mp
, gpointer data
)
564 /* printf("[ ref %p %d->%d ]\n", mp, mp->ref, mp->ref + 1); */
570 static void unref(MaskedPixmap
*mp
, gpointer data
)
572 /* printf("[ unref %p %d->%d ]\n", mp, mp->ref, mp->ref - 1); */
574 if (mp
&& --mp
->ref
== 0)
578 gdk_pixbuf_unref(mp
->huge_pixbuf
);
582 gdk_pixmap_unref(mp
->huge_pixmap
);
584 gdk_bitmap_unref(mp
->huge_mask
);
587 gdk_pixmap_unref(mp
->pixmap
);
589 gdk_bitmap_unref(mp
->mask
);
592 gdk_pixmap_unref(mp
->sm_pixmap
);
594 gdk_bitmap_unref(mp
->sm_mask
);
600 static int getref(MaskedPixmap
*mp
)
602 return mp
? mp
->ref
: 0;
605 /* Called now and then to clear out old pixmaps */
606 static gint
purge(gpointer data
)
608 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
613 static void got_thumb_data(GdkPixbufLoader
*loader
,
614 gint fd
, GdkInputCondition cond
)
622 got
= read(fd
, data
, sizeof(data
));
625 if (got
> 0 && gdk_pixbuf_loader_write(loader
, data
, got
, NULL
))
628 if (got
> 0 && gdk_pixbuf_loader_write(loader
, data
, got
))
632 /* Some kind of error, or end-of-file */
634 path
= GET_LOADER("thumb-path");
636 tag
= GPOINTER_TO_INT(GET_LOADER("thumb-input-tag"));
637 gdk_input_remove(tag
);
640 gdk_pixbuf_loader_close(loader
, NULL
);
642 gdk_pixbuf_loader_close(loader
);
650 pixbuf
= gdk_pixbuf_loader_get_pixbuf(loader
);
653 gdk_pixbuf_ref(pixbuf
);
654 image
= image_from_pixbuf(pixbuf
);
655 gdk_pixbuf_unref(pixbuf
);
657 g_fscache_insert(pixmap_cache
, path
, image
);
659 dir_force_update_path(path
);
667 callback
= GET_LOADER("thumb-callback");
668 cbdata
= GET_LOADER("thumb-callback-data");
670 callback(cbdata
, got
!= 0 ? NULL
: path
);
673 g_object_unref(G_OBJECT(loader
));
675 gtk_object_unref(GTK_OBJECT(loader
));