r1016: Minor fixes to thumbnails patch.
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blobcb05ed0ca51713745f3a2d599c65d2d0d699a976
1 /*
2 * $Id$
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)
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 pixmaps */
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
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
40 #include <gtk/gtk.h>
41 #include <gdk-pixbuf/gdk-pixbuf.h>
42 #include <gdk-pixbuf/gdk-pixbuf-loader.h>
44 #include "global.h"
46 #include "fscache.h"
47 #include "support.h"
48 #include "gui_support.h"
49 #include "pixmaps.h"
50 #include "main.h"
51 #include "filer.h"
52 #include "dir.h"
54 GFSCache *pixmap_cache = NULL;
55 FilerWindow *pixmap_cache_load_via = NULL;
57 static const char * bad_xpm[] = {
58 "12 12 3 1",
59 " c #000000000000",
60 ". c #FFFF00000000",
61 "x c #FFFFFFFFFFFF",
62 " ",
63 " ..xxxxxx.. ",
64 " ...xxxx... ",
65 " x...xx...x ",
66 " xx......xx ",
67 " xxx....xxx ",
68 " xxx....xxx ",
69 " xx......xx ",
70 " x...xx...x ",
71 " ...xxxx... ",
72 " ..xxxxxx.. ",
73 " "};
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)
109 #ifndef GTK2
110 gdk_rgb_init();
111 gtk_widget_push_visual(gdk_rgb_get_visual());
112 #endif
113 gtk_widget_push_colormap(gdk_rgb_get_cmap());
115 pixmap_cache = g_fscache_new((GFSLoadFunc) load,
116 (GFSRefFunc) ref,
117 (GFSRefFunc) unref,
118 (GFSGetRefFunc) getref,
119 NULL, /* Update func */
120 NULL);
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);
133 if (!retval)
134 retval = get_bad_image();
135 return retval;
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)
156 ref(mp, NULL);
159 void pixmap_unref(MaskedPixmap *mp)
161 unref(mp, NULL);
164 void pixmap_make_huge(MaskedPixmap *mp)
166 GdkPixbuf *hg;
168 if (mp->huge_pixmap)
169 return;
171 g_return_if_fail(mp->huge_pixbuf != NULL);
173 hg = scale_pixbuf_up(mp->huge_pixbuf,
174 HUGE_WIDTH * 0.7,
175 HUGE_HEIGHT * 0.7);
177 if (hg)
179 gdk_pixbuf_render_pixmap_and_mask(hg,
180 &mp->huge_pixmap,
181 &mp->huge_mask,
182 128);
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);
191 if (mp->mask)
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)
202 GdkPixbuf *sm;
204 if (mp->sm_pixmap)
205 return;
207 g_return_if_fail(mp->huge_pixbuf != NULL);
209 sm = scale_pixbuf(mp->huge_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
211 if (sm)
213 gdk_pixbuf_render_pixmap_and_mask(sm,
214 &mp->sm_pixmap,
215 &mp->sm_mask,
216 128);
217 mp->sm_width = gdk_pixbuf_get_width(sm);
218 mp->sm_height = gdk_pixbuf_get_height(sm);
219 gdk_pixbuf_unref(sm);
222 if (mp->sm_pixmap)
223 return;
225 gdk_pixmap_ref(mp->pixmap);
226 if (mp->mask)
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;
234 #ifdef GTK2
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))
239 #else
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))
245 #endif
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;
253 int fd, tag;
255 fd = mc_open(path, O_RDONLY | O_NONBLOCK);
256 if (fd == -1)
258 callback(data, NULL);
259 return;
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 ****************************************************************/
278 #ifdef GTK2
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)
284 struct stat info;
285 int original_width, original_height;
286 GString *to;
287 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
288 mode_t old_mask;
290 original_width = gdk_pixbuf_get_width(full);
291 original_height = gdk_pixbuf_get_height(full);
293 if (mc_stat(path, &info) != 0)
294 return;
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);
303 md5 = md5_hash(uri);
304 g_free(path);
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");
314 g_free(md5);
316 old_mask = umask(0077);
317 gdk_pixbuf_save(image->huge_pixbuf,
318 to->str,
319 "png",
320 NULL,
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,
327 NULL);
328 umask(old_mask);
330 g_string_free(to, TRUE);
331 g_free(swidth);
332 g_free(sheight);
333 g_free(ssize);
334 g_free(smtime);
335 g_free(uri);
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)
343 GdkPixbuf *thumb;
344 char *thumb_path, *md5, *uri;
345 char *ssize, *smtime;
346 struct stat info;
348 path = pathdup(path);
349 uri = g_strconcat("file://", path, NULL);
350 md5 = md5_hash(uri);
351 g_free(uri);
353 thumb_path = g_strdup_printf("%s/.thumbnails/96x96/%s.png",
354 home_dir, md5);
355 g_free(md5);
357 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
358 if (!thumb)
359 goto err;
361 /* Note that these don't need freeing... */
362 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
363 if (!ssize)
364 goto err;
366 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
367 if (!smtime)
368 goto err;
370 if (mc_stat(path, &info) != 0)
371 goto err;
373 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
374 goto err;
376 goto out;
377 err:
378 if (thumb)
379 gdk_pixbuf_unref(thumb);
380 thumb = NULL;
381 out:
382 g_free(path);
383 g_free(thumb_path);
385 return thumb;
388 #endif
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)
395 GdkPixbuf *pixbuf;
396 MaskedPixmap *image;
397 #ifdef GTK2
398 GError *error = NULL;
400 pixbuf = get_thumbnail_for(path);
401 if (!pixbuf)
402 pixbuf = gdk_pixbuf_new_from_file(path, &error);
403 if (!pixbuf)
405 g_print("%s\n", error ? error->message : _("Unknown error"));
406 g_error_free(error);
407 return NULL;
409 #else
410 pixbuf = gdk_pixbuf_new_from_file(path);
411 if (!pixbuf)
412 return NULL;
413 #endif
415 image = image_from_pixbuf(pixbuf);
417 #ifdef GTK2
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);
422 #endif
424 gdk_pixbuf_unref(pixbuf);
426 return image;
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)
434 int w, h;
436 w = gdk_pixbuf_get_width(src);
437 h = gdk_pixbuf_get_height(src);
439 if (w <= max_w && h <= max_h)
441 gdk_pixbuf_ref(src);
442 return src;
444 else
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,
453 MAX(dest_w, 1),
454 MAX(dest_h, 1),
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)
464 int w, 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))
471 gdk_pixbuf_ref(src);
472 return src;
474 else
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,
481 w * scale,
482 h * scale,
483 GDK_INTERP_BILINEAR);
487 /* Turn a full-size pixbuf into a MaskedPixmap */
488 MaskedPixmap *image_from_pixbuf(GdkPixbuf *full_size)
490 MaskedPixmap *mp;
491 GdkPixbuf *huge_pixbuf, *normal_pixbuf;
492 GdkPixmap *pixmap;
493 GdkBitmap *mask;
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);
505 if (!pixmap)
507 gdk_pixbuf_unref(huge_pixbuf);
508 gdk_pixbuf_unref(normal_pixbuf);
509 return NULL;
512 mp = g_new(MaskedPixmap, 1);
513 mp->huge_pixbuf = huge_pixbuf;
514 mp->ref = 1;
516 mp->pixmap = pixmap;
517 mp->mask = mask;
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;
525 mp->huge_width = -1;
526 mp->huge_height = -1;
528 mp->sm_pixmap = NULL;
529 mp->sm_mask = NULL;
530 mp->sm_width = -1;
531 mp->sm_height = -1;
533 return mp;
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)
541 GdkPixbuf *bad;
542 MaskedPixmap *mp;
544 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
545 mp = image_from_pixbuf(bad);
546 gdk_pixbuf_unref(bad);
548 return mp;
551 static MaskedPixmap *load(char *pathname, gpointer user_data)
553 if (pixmap_cache_load_via)
555 filer_create_thumb(pixmap_cache_load_via, pathname);
556 return NULL;
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); */
566 if (mp)
567 mp->ref++;
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)
576 if (mp->huge_pixbuf)
578 gdk_pixbuf_unref(mp->huge_pixbuf);
581 if (mp->huge_pixmap)
582 gdk_pixmap_unref(mp->huge_pixmap);
583 if (mp->huge_mask)
584 gdk_bitmap_unref(mp->huge_mask);
586 if (mp->pixmap)
587 gdk_pixmap_unref(mp->pixmap);
588 if (mp->mask)
589 gdk_bitmap_unref(mp->mask);
591 if (mp->sm_pixmap)
592 gdk_pixmap_unref(mp->sm_pixmap);
593 if (mp->sm_mask)
594 gdk_bitmap_unref(mp->sm_mask);
596 g_free(mp);
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);
610 return TRUE;
613 static void got_thumb_data(GdkPixbufLoader *loader,
614 gint fd, GdkInputCondition cond)
616 GFunc callback;
617 gchar *path;
618 gpointer cbdata;
619 char data[4096];
620 int got, tag;
622 got = read(fd, data, sizeof(data));
624 #ifdef GTK2
625 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got, NULL))
626 return;
627 #else
628 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got))
629 return;
630 #endif
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);
639 #ifdef GTK2
640 gdk_pixbuf_loader_close(loader, NULL);
641 #else
642 gdk_pixbuf_loader_close(loader);
643 #endif
645 if (got == 0)
647 MaskedPixmap *image;
648 GdkPixbuf *pixbuf;
650 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
651 if (pixbuf)
653 gdk_pixbuf_ref(pixbuf);
654 image = image_from_pixbuf(pixbuf);
655 gdk_pixbuf_unref(pixbuf);
657 g_fscache_insert(pixmap_cache, path, image);
658 pixmap_unref(image);
659 dir_force_update_path(path);
661 else
662 got = -1;
665 mc_close(fd);
667 callback = GET_LOADER("thumb-callback");
668 cbdata = GET_LOADER("thumb-callback-data");
670 callback(cbdata, got != 0 ? NULL : path);
672 #ifdef GTK2
673 g_object_unref(G_OBJECT(loader));
674 #else
675 gtk_object_unref(GTK_OBJECT(loader));
676 #endif