r1322: Converted MaskedPixmap to GObject.
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blob1ec6c9f72e9d8eaa19dfec128909f94e316dbc8a
1 /*
2 * $Id$
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)
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;
56 static const char * bad_xpm[] = {
57 "12 12 3 1",
58 " c #000000000000",
59 ". c #FFFF00000000",
60 "x c #FFFFFFFFFFFF",
61 " ",
62 " ..xxxxxx.. ",
63 " ...xxxx... ",
64 " x...xx...x ",
65 " xx......xx ",
66 " xxx....xxx ",
67 " xxx....xxx ",
68 " xx......xx ",
69 " x...xx...x ",
70 " ...xxxx... ",
71 " ..xxxxxx.. ",
72 " "};
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 */
112 NULL);
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);
125 if (!retval)
126 retval = get_bad_image();
127 return retval;
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)
148 GdkPixbuf *hg;
150 if (mp->huge_pixmap)
151 return;
153 g_return_if_fail(mp->huge_pixbuf != NULL);
155 hg = scale_pixbuf_up(mp->huge_pixbuf,
156 HUGE_WIDTH * 0.7,
157 HUGE_HEIGHT * 0.7);
159 if (hg)
161 gdk_pixbuf_render_pixmap_and_mask(hg,
162 &mp->huge_pixmap,
163 &mp->huge_mask,
164 128);
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);
173 if (mp->mask)
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)
184 GdkPixbuf *sm;
186 if (mp->sm_pixmap)
187 return;
189 g_return_if_fail(mp->huge_pixbuf != NULL);
191 sm = scale_pixbuf(mp->huge_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
193 if (sm)
195 gdk_pixbuf_render_pixmap_and_mask(sm,
196 &mp->sm_pixmap,
197 &mp->sm_mask,
198 128);
199 mp->sm_width = gdk_pixbuf_get_width(sm);
200 mp->sm_height = gdk_pixbuf_get_height(sm);
201 gdk_pixbuf_unref(sm);
204 if (mp->sm_pixmap)
205 return;
207 gdk_pixmap_ref(mp->pixmap);
208 if (mp->mask)
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;
230 int fd, tag;
231 gboolean found;
232 MaskedPixmap *image;
233 GdkPixbuf *pixbuf;
235 image = g_fscache_lookup_full(pixmap_cache, path,
236 FSCACHE_LOOKUP_ONLY_NEW, &found);
238 if (found)
240 /* Thumbnail is known, or being created */
241 g_fscache_data_unref(pixmap_cache, image);
242 callback(data, NULL);
243 return;
246 g_return_if_fail(image == NULL);
248 pixbuf = get_thumbnail_for(path);
249 if (pixbuf)
251 MaskedPixmap *image;
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);
257 return;
260 /* Add an entry, set to NULL, so no-one else tries to load this
261 * image.
263 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
265 fd = mc_open(path, O_RDONLY | O_NONBLOCK);
266 if (fd == -1)
268 callback(data, NULL);
269 return;
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)
293 struct stat info;
294 int original_width, original_height;
295 GString *to;
296 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
297 mode_t old_mask;
298 int name_len;
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))
303 return;
305 original_width = gdk_pixbuf_get_width(full);
306 original_height = gdk_pixbuf_get_height(full);
308 if (mc_stat(path, &info) != 0)
309 return;
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);
318 md5 = md5_hash(uri);
319 g_free(path);
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());
330 g_free(md5);
332 old_mask = umask(0077);
333 gdk_pixbuf_save(image->huge_pixbuf,
334 to->str,
335 "png",
336 NULL,
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,
343 NULL);
344 umask(old_mask);
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
348 * once.
351 gchar *final;
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));
357 g_free(final);
360 g_string_free(to, TRUE);
361 g_free(swidth);
362 g_free(sheight);
363 g_free(ssize);
364 g_free(smtime);
365 g_free(uri);
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;
376 struct stat info;
378 path = pathdup(pathname);
379 uri = g_strconcat("file://", path, NULL);
380 md5 = md5_hash(uri);
381 g_free(uri);
383 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
384 home_dir, md5);
385 g_free(md5);
387 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
388 if (!thumb)
389 goto err;
391 /* Note that these don't need freeing... */
392 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
393 if (!ssize)
394 goto err;
396 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
397 if (!smtime)
398 goto err;
400 if (mc_stat(path, &info) != 0)
401 goto err;
403 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
404 goto err;
406 goto out;
407 err:
408 if (thumb)
409 gdk_pixbuf_unref(thumb);
410 thumb = NULL;
411 out:
412 g_free(path);
413 g_free(thumb_path);
414 return 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)
423 GdkPixbuf *pixbuf;
424 MaskedPixmap *image;
425 GError *error = NULL;
427 pixbuf = gdk_pixbuf_new_from_file(path, &error);
428 if (!pixbuf)
430 g_print("%s\n", error ? error->message : _("Unknown error"));
431 g_error_free(error);
432 return NULL;
435 image = masked_pixmap_new(pixbuf);
437 gdk_pixbuf_unref(pixbuf);
439 return image;
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)
447 int w, h;
449 w = gdk_pixbuf_get_width(src);
450 h = gdk_pixbuf_get_height(src);
452 if (w <= max_w && h <= max_h)
454 gdk_pixbuf_ref(src);
455 return src;
457 else
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,
466 MAX(dest_w, 1),
467 MAX(dest_h, 1),
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)
477 int w, 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))
484 gdk_pixbuf_ref(src);
485 return src;
487 else
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,
494 w * scale,
495 h * scale,
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)
505 GdkPixbuf *bad;
506 MaskedPixmap *mp;
508 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
509 mp = masked_pixmap_new(bad);
510 gdk_pixbuf_unref(bad);
512 return mp;
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);
520 return TRUE;
523 static void got_thumb_data(GdkPixbufLoader *loader,
524 gint fd, GdkInputCondition cond)
526 GFunc callback;
527 gchar *path;
528 gpointer cbdata;
529 char data[4096];
530 int got, tag;
532 got = read(fd, data, sizeof(data));
534 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got, NULL))
535 return;
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);
546 if (got == 0)
548 MaskedPixmap *image;
549 GdkPixbuf *pixbuf;
551 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
552 if (pixbuf)
554 gdk_pixbuf_ref(pixbuf);
555 image = masked_pixmap_new(pixbuf);
557 g_fscache_insert(pixmap_cache, path, image, FALSE);
558 if (image)
560 save_thumbnail(path, pixbuf, image);
561 g_object_unref(image);
563 gdk_pixbuf_unref(pixbuf);
565 else
566 got = -1;
569 mc_close(fd);
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");
587 if (mp->huge_pixbuf)
589 gdk_pixbuf_unref(mp->huge_pixbuf);
590 mp->huge_pixbuf = NULL;
593 if (mp->huge_pixmap)
595 gdk_pixmap_unref(mp->huge_pixmap);
596 mp->huge_pixmap = NULL;
598 if (mp->huge_mask)
600 gdk_bitmap_unref(mp->huge_mask);
601 mp->huge_mask = NULL;
604 if (mp->pixmap)
606 g_object_unref(mp->pixmap);
607 mp->pixmap = NULL;
609 if (mp->mask)
611 g_object_unref(mp->mask);
612 mp->mask = NULL;
615 if (mp->sm_pixmap)
617 g_object_unref(mp->sm_pixmap);
618 mp->sm_pixmap = NULL;
620 if (mp->sm_mask)
622 g_object_unref(mp->sm_mask);
623 mp->sm_mask = NULL;
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;
646 mp->huge_width = -1;
647 mp->huge_height = -1;
649 mp->pixmap = NULL;
650 mp->mask = NULL;
651 mp->width = -1;
652 mp->height = -1;
654 mp->sm_pixmap = NULL;
655 mp->sm_mask = NULL;
656 mp->sm_width = -1;
657 mp->sm_height = -1;
660 static GType masked_pixmap_get_type(void)
662 static GType type = 0;
664 if (!type)
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),
675 0, /* n_preallocs */
676 masked_pixmap_init
679 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
680 &info, 0);
683 return type;
686 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
688 MaskedPixmap *mp;
689 GdkPixbuf *huge_pixbuf, *normal_pixbuf;
690 GdkPixmap *pixmap;
691 GdkBitmap *mask;
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);
703 if (!pixmap)
705 gdk_pixbuf_unref(huge_pixbuf);
706 gdk_pixbuf_unref(normal_pixbuf);
707 return NULL;
710 mp = g_object_new(masked_pixmap_get_type(), NULL);
712 mp->huge_pixbuf = huge_pixbuf;
714 mp->pixmap = pixmap;
715 mp->mask = mask;
716 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
717 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
719 gdk_pixbuf_unref(normal_pixbuf);
721 return mp;