Don't use "cp -u" in build script; doesn't work on FreeBSD
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blobd2665912772ede80d6fd7f02096fc0df3c43f408
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* pixmaps.c - code for handling pixbufs (despite the name!) */
22 #include "config.h"
23 #define PIXMAPS_C
25 /* Remove pixmaps from the cache when they haven't been accessed for
26 * this period of time (seconds).
29 #define PIXMAP_PURGE_TIME 1200
30 #define PIXMAP_THUMB_SIZE 128
31 #define PIXMAP_THUMB_TOO_OLD_TIME 5
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>
39 #include <string.h>
41 #include <gtk/gtk.h>
43 #include "global.h"
45 #include "fscache.h"
46 #include "support.h"
47 #include "gui_support.h"
48 #include "pixmaps.h"
49 #include "main.h"
50 #include "filer.h"
51 #include "dir.h"
52 #include "diritem.h"
53 #include "choices.h"
54 #include "options.h"
55 #include "action.h"
56 #include "type.h"
58 GFSCache *pixmap_cache = NULL;
59 GFSCache *desktop_icon_cache = NULL;
61 static const char * bad_xpm[] = {
62 "12 12 3 1",
63 " c #000000000000",
64 ". c #FFFF00000000",
65 "x c #FFFFFFFFFFFF",
66 " ",
67 " ..xxxxxx.. ",
68 " ...xxxx... ",
69 " x...xx...x ",
70 " xx......xx ",
71 " xxx....xxx ",
72 " xxx....xxx ",
73 " xx......xx ",
74 " x...xx...x ",
75 " ...xxxx... ",
76 " ..xxxxxx.. ",
77 " "};
79 MaskedPixmap *im_error;
80 MaskedPixmap *im_unknown;
82 MaskedPixmap *im_appdir;
84 MaskedPixmap *im_dirs;
86 GtkIconSize mount_icon_size = -1;
88 typedef struct _ChildThumbnail ChildThumbnail;
90 /* There is one of these for each active child process */
91 struct _ChildThumbnail {
92 gchar *path;
93 GFunc callback;
94 gpointer data;
97 static const char *stocks[] = {
98 ROX_STOCK_SHOW_DETAILS,
99 ROX_STOCK_SHOW_HIDDEN,
100 ROX_STOCK_SELECT,
101 ROX_STOCK_MOUNT,
102 ROX_STOCK_MOUNTED,
103 ROX_STOCK_SYMLINK,
104 ROX_STOCK_XATTR,
107 /* Static prototypes */
109 static void load_default_pixmaps(void);
110 static gint purge(gpointer data);
111 static MaskedPixmap *image_from_file(const char *path);
112 static MaskedPixmap *image_from_desktop_file(const char *path);
113 static MaskedPixmap *get_bad_image(void);
114 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
115 static GdkPixbuf *get_thumbnail_for(const char *path);
116 static void thumbnail_child_done(ChildThumbnail *info);
117 static void child_create_thumbnail(const gchar *path, MIME_type *type);
118 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label);
119 static gchar *thumbnail_path(const gchar *path);
120 static gchar *thumbnail_program(MIME_type *type);
121 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path);
123 /****************************************************************
124 * EXTERNAL INTERFACE *
125 ****************************************************************/
127 void pixmaps_init(void)
129 GtkIconFactory *factory;
130 int i;
132 gtk_widget_push_colormap(gdk_rgb_get_colormap());
134 pixmap_cache = g_fscache_new((GFSLoadFunc) image_from_file, NULL, NULL);
135 desktop_icon_cache = g_fscache_new((GFSLoadFunc) image_from_desktop_file, NULL, NULL);
137 g_timeout_add(10000, purge, NULL);
139 factory = gtk_icon_factory_new();
140 for (i = 0; i < G_N_ELEMENTS(stocks); i++)
142 GdkPixbuf *pixbuf;
143 GError *error = NULL;
144 gchar *path;
145 GtkIconSet *iset;
146 const gchar *name = stocks[i];
148 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
149 pixbuf = gdk_pixbuf_new_from_file(path, &error);
150 if (!pixbuf)
152 g_warning("%s", error->message);
153 g_error_free(error);
154 pixbuf = gdk_pixbuf_new_from_xpm_data(bad_xpm);
156 g_free(path);
158 iset = gtk_icon_set_new_from_pixbuf(pixbuf);
159 g_object_unref(G_OBJECT(pixbuf));
160 gtk_icon_factory_add(factory, name, iset);
161 gtk_icon_set_unref(iset);
163 gtk_icon_factory_add_default(factory);
165 mount_icon_size = gtk_icon_size_register("rox-mount-size", 14, 14);
167 load_default_pixmaps();
169 option_register_widget("thumbs-purge-cache", thumbs_purge_cache);
172 /* Load image <appdir>/images/name.png.
173 * Always returns with a valid image.
175 MaskedPixmap *load_pixmap(const char *name)
177 guchar *path;
178 MaskedPixmap *retval;
180 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
181 retval = image_from_file(path);
182 g_free(path);
184 if (!retval)
185 retval = get_bad_image();
187 return retval;
190 /* Create a MaskedPixmap from a GTK stock ID. Always returns
191 * a valid image.
193 static MaskedPixmap *mp_from_stock(const char *stock_id, int size)
195 GtkIconSet *icon_set;
196 GdkPixbuf *pixbuf;
197 MaskedPixmap *retval;
199 /*icon_set = gtk_icon_factory_lookup_default(stock_id);*/
200 icon_set = gtk_style_lookup_icon_set(gtk_widget_get_default_style(),
201 stock_id);
202 if (!icon_set)
203 return get_bad_image();
205 pixbuf = gtk_icon_set_render_icon(icon_set,
206 gtk_widget_get_default_style(), /* Gtk bug */
207 GTK_TEXT_DIR_LTR,
208 GTK_STATE_NORMAL,
209 size,
210 NULL,
211 NULL);
212 retval = masked_pixmap_new(pixbuf);
213 gdk_pixbuf_unref(pixbuf);
215 return retval;
218 void pixmap_make_huge(MaskedPixmap *mp)
220 if (mp->huge_pixbuf)
221 return;
223 g_return_if_fail(mp->src_pixbuf != NULL);
225 /* Limit to small size now, otherwise they get scaled up in mixed mode.
226 * Also looked ugly.
228 mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
229 SMALL_WIDTH, SMALL_HEIGHT);
231 if (!mp->huge_pixbuf)
233 mp->huge_pixbuf = mp->src_pixbuf;
234 g_object_ref(mp->huge_pixbuf);
237 mp->huge_width = gdk_pixbuf_get_width(mp->huge_pixbuf);
238 mp->huge_height = gdk_pixbuf_get_height(mp->huge_pixbuf);
241 void pixmap_make_small(MaskedPixmap *mp)
243 if (mp->sm_pixbuf)
244 return;
246 g_return_if_fail(mp->src_pixbuf != NULL);
248 mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
250 if (!mp->sm_pixbuf)
252 mp->sm_pixbuf = mp->src_pixbuf;
253 g_object_ref(mp->sm_pixbuf);
256 mp->sm_width = gdk_pixbuf_get_width(mp->sm_pixbuf);
257 mp->sm_height = gdk_pixbuf_get_height(mp->sm_pixbuf);
260 /* Load image 'path' in the background and insert into pixmap_cache.
261 * Call callback(data, path) when done (path is NULL => error).
262 * If the image is already uptodate, or being created already, calls the
263 * callback right away.
265 void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data)
267 gboolean found;
268 MaskedPixmap *image;
269 GdkPixbuf *pixbuf;
270 pid_t child;
271 ChildThumbnail *info;
272 MIME_type *type;
273 gchar *thumb_prog;
276 image = g_fscache_lookup_full(pixmap_cache, path,
277 FSCACHE_LOOKUP_ONLY_NEW, &found);
279 if (found)
281 /* Thumbnail is known, or being created */
282 if (image)
283 g_object_unref(image);
284 callback(data, NULL);
285 return;
288 g_return_if_fail(image == NULL);
290 pixbuf = get_thumbnail_for(path);
292 if (!pixbuf)
294 struct stat info1, info2;
295 char *dir;
297 // Skip zero-byte files. They're either empty, or special (may cause
298 // us to hang, e.g. /proc/kmsg).
299 if (mc_stat(path, &info1) == 0 && info1.st_size == 0) {
300 callback(data, NULL);
301 return;
304 dir = g_path_get_dirname(path);
306 /* If the image itself is in ~/.thumbnails, load it now
307 * (ie, don't create thumbnails for thumbnails!).
309 if (mc_stat(dir, &info1) != 0)
311 callback(data, NULL);
312 g_free(dir);
313 return;
315 g_free(dir);
317 if (mc_stat(make_path(home_dir, ".thumbnails/normal"),
318 &info2) == 0 &&
319 info1.st_dev == info2.st_dev &&
320 info1.st_ino == info2.st_ino)
322 pixbuf = rox_pixbuf_new_from_file_at_scale(path,
323 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
324 if (!pixbuf)
326 g_fscache_insert(pixmap_cache,
327 path, NULL, TRUE);
328 callback(data, NULL);
329 return;
334 if (pixbuf)
336 MaskedPixmap *image;
338 image = masked_pixmap_new(pixbuf);
339 gdk_pixbuf_unref(pixbuf);
340 g_fscache_insert(pixmap_cache, path, image, TRUE);
341 callback(data, (gchar *) path);
342 g_object_unref(G_OBJECT(image));
343 return;
346 type = type_from_path(path);
347 if (!type)
348 type = text_plain;
350 /* Add an entry, set to NULL, so no-one else tries to load this
351 * image.
353 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
355 thumb_prog = thumbnail_program(type);
357 /* Only attempt to load 'images' types ourselves */
358 if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0)
360 callback(data, NULL);
361 return; /* Don't know how to handle this type */
364 child = fork();
366 if (child == -1)
368 g_free(thumb_prog);
369 delayed_error("fork(): %s", g_strerror(errno));
370 callback(data, NULL);
371 return;
374 if (child == 0)
376 /* We are the child process. (We are sloppy with freeing
377 memory, but since we go away very quickly, that's ok.) */
378 if (thumb_prog)
380 DirItem *item;
382 item = diritem_new(g_basename(thumb_prog));
384 diritem_restat(thumb_prog, item, NULL);
385 if (item->flags & ITEM_FLAG_APPDIR)
386 thumb_prog = g_strconcat(thumb_prog, "/AppRun",
387 NULL);
389 execl(thumb_prog, thumb_prog, path,
390 thumbnail_path(path),
391 g_strdup_printf("%d", PIXMAP_THUMB_SIZE),
392 NULL);
393 _exit(1);
396 child_create_thumbnail(path, type);
397 _exit(0);
400 g_free(thumb_prog);
402 info = g_new(ChildThumbnail, 1);
403 info->path = g_strdup(path);
404 info->callback = callback;
405 info->data = data;
406 on_child_death(child, (CallbackFn) thumbnail_child_done, info);
409 /****************************************************************
410 * INTERNAL FUNCTIONS *
411 ****************************************************************/
413 /* Create a thumbnail file for this image */
414 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
416 struct stat info;
417 gchar *path;
418 int original_width, original_height;
419 GString *to;
420 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
421 mode_t old_mask;
422 int name_len;
423 GdkPixbuf *thumb;
425 thumb = scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
427 original_width = gdk_pixbuf_get_width(full);
428 original_height = gdk_pixbuf_get_height(full);
430 if (mc_stat(pathname, &info) != 0)
431 return;
433 swidth = g_strdup_printf("%d", original_width);
434 sheight = g_strdup_printf("%d", original_height);
435 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
436 smtime = g_strdup_printf("%ld", (long) info.st_mtime);
438 path = pathdup(pathname);
439 uri = g_filename_to_uri(path, NULL, NULL);
440 if (!uri)
441 uri = g_strconcat("file://", path, NULL);
442 md5 = md5_hash(uri);
443 g_free(path);
445 to = g_string_new(home_dir);
446 g_string_append(to, "/.thumbnails");
447 mkdir(to->str, 0700);
448 g_string_append(to, "/normal/");
449 mkdir(to->str, 0700);
450 g_string_append(to, md5);
451 name_len = to->len + 4; /* Truncate to this length when renaming */
452 g_string_append_printf(to, ".png.ROX-Filer-%ld", (long) getpid());
454 g_free(md5);
456 old_mask = umask(0077);
457 gdk_pixbuf_save(thumb, to->str, "png", NULL,
458 "tEXt::Thumb::Image::Width", swidth,
459 "tEXt::Thumb::Image::Height", sheight,
460 "tEXt::Thumb::Size", ssize,
461 "tEXt::Thumb::MTime", smtime,
462 "tEXt::Thumb::URI", uri,
463 "tEXt::Software", PROJECT,
464 NULL);
465 umask(old_mask);
467 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
468 * a race condition if two programs create the same thumb at
469 * once.
472 gchar *final;
474 final = g_strndup(to->str, name_len);
475 if (rename(to->str, final))
476 g_warning("Failed to rename '%s' to '%s': %s",
477 to->str, final, g_strerror(errno));
478 g_free(final);
481 g_string_free(to, TRUE);
482 g_free(swidth);
483 g_free(sheight);
484 g_free(ssize);
485 g_free(smtime);
486 g_free(uri);
489 static gchar *thumbnail_path(const char *path)
491 gchar *uri, *md5;
492 GString *to;
493 gchar *ans;
495 uri = g_filename_to_uri(path, NULL, NULL);
496 if(!uri)
497 uri = g_strconcat("file://", path, NULL);
498 md5 = md5_hash(uri);
500 to = g_string_new(home_dir);
501 g_string_append(to, "/.thumbnails");
502 mkdir(to->str, 0700);
503 g_string_append(to, "/normal/");
504 mkdir(to->str, 0700);
505 g_string_append(to, md5);
506 g_string_append(to, ".png");
508 g_free(md5);
509 g_free(uri);
511 ans=to->str;
512 g_string_free(to, FALSE);
514 return ans;
517 /* Return a program to create thumbnails for files of this type.
518 * NULL to try to make it ourself (using gdk).
519 * g_free the result.
521 static gchar *thumbnail_program(MIME_type *type)
523 gchar *leaf;
524 gchar *path;
526 if (!type)
527 return NULL;
529 leaf = g_strconcat(type->media_type, "_", type->subtype, NULL);
530 path = choices_find_xdg_path_load(leaf, "MIME-thumb", SITE);
531 g_free(leaf);
532 if (path)
534 return path;
537 path = choices_find_xdg_path_load(type->media_type, "MIME-thumb",
538 SITE);
540 return path;
543 /* Called in a subprocess. Load path and create the thumbnail
544 * file. Parent will notice when we die.
546 static void child_create_thumbnail(const gchar *path, MIME_type *type)
548 GdkPixbuf *image=NULL;
550 if(strcmp(type->subtype, "jpeg")==0)
551 image=extract_tiff_thumbnail(path);
553 if(!image)
554 image = rox_pixbuf_new_from_file_at_scale(path,
555 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
557 if (image)
558 save_thumbnail(path, image);
560 /* (no need to unref, as we're about to exit) */
563 /* Called when the child process exits */
564 static void thumbnail_child_done(ChildThumbnail *info)
566 GdkPixbuf *thumb;
568 thumb = get_thumbnail_for(info->path);
570 if (thumb)
572 MaskedPixmap *image;
574 image = masked_pixmap_new(thumb);
575 g_object_unref(thumb);
577 g_fscache_insert(pixmap_cache, info->path, image, FALSE);
578 g_object_unref(image);
580 info->callback(info->data, info->path);
582 else
583 info->callback(info->data, NULL);
585 g_free(info->path);
586 g_free(info);
589 /* Check if we have an up-to-date thumbnail for this image.
590 * If so, return it. Otherwise, returns NULL.
592 static GdkPixbuf *get_thumbnail_for(const char *pathname)
594 GdkPixbuf *thumb = NULL;
595 char *thumb_path, *md5, *uri, *path;
596 const char *ssize, *smtime;
597 struct stat info;
598 time_t ttime, now;
600 path = pathdup(pathname);
601 uri = g_filename_to_uri(path, NULL, NULL);
602 if(!uri)
603 uri = g_strconcat("file://", path, NULL);
604 md5 = md5_hash(uri);
605 g_free(uri);
607 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
608 home_dir, md5);
609 g_free(md5);
611 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
612 if (!thumb)
613 goto err;
615 /* Note that these don't need freeing... */
616 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
617 /* This is optional, so don't flag an error if it is missing */
619 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
620 if (!smtime)
621 goto err;
623 if (mc_stat(path, &info) != 0)
624 goto err;
626 ttime=(time_t) atol(smtime);
627 time(&now);
628 if (info.st_mtime != ttime && now>ttime+PIXMAP_THUMB_TOO_OLD_TIME)
629 goto err;
631 if (ssize && info.st_size < atol(ssize))
632 goto err;
634 goto out;
635 err:
636 if (thumb)
637 gdk_pixbuf_unref(thumb);
638 thumb = NULL;
639 out:
640 g_free(path);
641 g_free(thumb_path);
642 return thumb;
645 /* Load the image 'path' and return a pointer to the resulting
646 * MaskedPixmap. NULL on failure.
647 * Doesn't check for thumbnails (this is for small icons).
649 static MaskedPixmap *image_from_file(const char *path)
651 GdkPixbuf *pixbuf;
652 MaskedPixmap *image;
653 GError *error = NULL;
655 pixbuf = gdk_pixbuf_new_from_file(path, &error);
656 if (!pixbuf)
658 g_warning("%s\n", error->message);
659 g_error_free(error);
660 return NULL;
663 image = masked_pixmap_new(pixbuf);
665 gdk_pixbuf_unref(pixbuf);
667 return image;
670 /* Load this icon named by this .desktop file from the current theme.
671 * NULL on failure.
673 static MaskedPixmap *image_from_desktop_file(const char *path)
675 GError *error = NULL;
676 MaskedPixmap *image = NULL;
677 char *icon = NULL;
679 icon = get_value_from_desktop_file(path,
680 "Desktop Entry", "Icon", &error);
681 if (error)
683 g_warning("Failed to parse .desktop file '%s':\n%s",
684 path, error->message);
685 goto err;
687 if (!icon)
688 goto err;
690 if (icon[0] == '/')
691 image = image_from_file(icon);
692 else
694 GdkPixbuf *pixbuf;
695 int tmp_fd;
696 char *extension;
698 /* For some unknown reason, some icon names have extensions.
699 * Remove them.
701 extension = strrchr(icon, '.');
702 if (extension && (strcmp(extension, ".png") == 0
703 || strcmp(extension, ".xpm") == 0
704 || strcmp(extension, ".svg") == 0))
706 *extension = '\0';
709 /* SVG reader is very noisy, so redirect stderr to stdout */
710 tmp_fd = dup(2);
711 dup2(1, 2);
712 pixbuf = theme_load_icon(icon, HUGE_WIDTH, 0, NULL);
713 dup2(tmp_fd, 2);
714 close(tmp_fd);
716 if (pixbuf == NULL)
717 goto err; /* Might just not be in the theme */
719 image = masked_pixmap_new(pixbuf);
720 g_object_unref(pixbuf);
722 err:
723 if (error != NULL)
724 g_error_free(error);
725 if (icon != NULL)
726 g_free(icon);
727 return image;
730 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
731 * If src is small enough, then ref it and return that.
733 GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
735 int w, h;
737 w = gdk_pixbuf_get_width(src);
738 h = gdk_pixbuf_get_height(src);
740 if (w <= max_w && h <= max_h)
742 gdk_pixbuf_ref(src);
743 return src;
745 else
747 float scale_x = ((float) w) / max_w;
748 float scale_y = ((float) h) / max_h;
749 float scale = MAX(scale_x, scale_y);
750 int dest_w = w / scale;
751 int dest_h = h / scale;
753 return gdk_pixbuf_scale_simple(src,
754 MAX(dest_w, 1),
755 MAX(dest_h, 1),
756 GDK_INTERP_BILINEAR);
760 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
761 * If src is that size or bigger, then ref it and return that.
763 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
765 int w, h;
767 w = gdk_pixbuf_get_width(src);
768 h = gdk_pixbuf_get_height(src);
770 if (w == 0 || h == 0 || w >= max_w || h >= max_h)
772 gdk_pixbuf_ref(src);
773 return src;
775 else
777 float scale_x = max_w / ((float) w);
778 float scale_y = max_h / ((float) h);
779 float scale = MIN(scale_x, scale_y);
781 return gdk_pixbuf_scale_simple(src,
782 w * scale,
783 h * scale,
784 GDK_INTERP_BILINEAR);
788 /* Return a pointer to the (static) bad image. The ref counter will ensure
789 * that the image is never freed.
791 static MaskedPixmap *get_bad_image(void)
793 GdkPixbuf *bad;
794 MaskedPixmap *mp;
796 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
797 mp = masked_pixmap_new(bad);
798 gdk_pixbuf_unref(bad);
800 return mp;
803 /* Called now and then to clear out old pixmaps */
804 static gint purge(gpointer data)
806 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
808 return TRUE;
811 static gpointer parent_class;
813 static void masked_pixmap_finialize(GObject *object)
815 MaskedPixmap *mp = (MaskedPixmap *) object;
817 if (mp->src_pixbuf)
819 g_object_unref(mp->src_pixbuf);
820 mp->src_pixbuf = NULL;
823 if (mp->huge_pixbuf)
825 g_object_unref(mp->huge_pixbuf);
826 mp->huge_pixbuf = NULL;
828 if (mp->pixbuf)
830 g_object_unref(mp->pixbuf);
831 mp->pixbuf = NULL;
834 if (mp->sm_pixbuf)
836 g_object_unref(mp->sm_pixbuf);
837 mp->sm_pixbuf = NULL;
840 G_OBJECT_CLASS(parent_class)->finalize(object);
843 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
845 GObjectClass *object = (GObjectClass *) gclass;
847 parent_class = g_type_class_peek_parent(gclass);
849 object->finalize = masked_pixmap_finialize;
852 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
854 MaskedPixmap *mp = (MaskedPixmap *) object;
856 mp->src_pixbuf = NULL;
858 mp->huge_pixbuf = NULL;
859 mp->huge_width = -1;
860 mp->huge_height = -1;
862 mp->pixbuf = NULL;
863 mp->width = -1;
864 mp->height = -1;
866 mp->sm_pixbuf = NULL;
867 mp->sm_width = -1;
868 mp->sm_height = -1;
871 static GType masked_pixmap_get_type(void)
873 static GType type = 0;
875 if (!type)
877 static const GTypeInfo info =
879 sizeof (MaskedPixmapClass),
880 NULL, /* base_init */
881 NULL, /* base_finalise */
882 masked_pixmap_class_init,
883 NULL, /* class_finalise */
884 NULL, /* class_data */
885 sizeof(MaskedPixmap),
886 0, /* n_preallocs */
887 masked_pixmap_init
890 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
891 &info, 0);
894 return type;
897 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
899 MaskedPixmap *mp;
900 GdkPixbuf *src_pixbuf, *normal_pixbuf;
902 g_return_val_if_fail(full_size != NULL, NULL);
904 src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
905 g_return_val_if_fail(src_pixbuf != NULL, NULL);
907 normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
908 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
910 mp = g_object_new(masked_pixmap_get_type(), NULL);
912 mp->src_pixbuf = src_pixbuf;
914 mp->pixbuf = normal_pixbuf;
915 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
916 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
918 return mp;
921 /* Load all the standard pixmaps. Also sets the default window icon. */
922 static void load_default_pixmaps(void)
924 GdkPixbuf *pixbuf;
925 GError *error = NULL;
927 im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING,
928 GTK_ICON_SIZE_DIALOG);
929 im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION,
930 GTK_ICON_SIZE_DIALOG);
932 im_dirs = load_pixmap("dirs");
933 im_appdir = load_pixmap("application");
935 pixbuf = gdk_pixbuf_new_from_file(
936 make_path(app_dir, ".DirIcon"), &error);
937 if (pixbuf)
939 GList *icon_list;
941 icon_list = g_list_append(NULL, pixbuf);
942 gtk_window_set_default_icon_list(icon_list);
943 g_list_free(icon_list);
945 g_object_unref(G_OBJECT(pixbuf));
947 else
949 g_warning("%s\n", error->message);
950 g_error_free(error);
954 /* Also purges memory cache */
955 static void purge_disk_cache(GtkWidget *button, gpointer data)
957 char *path;
958 GList *list = NULL;
959 DIR *dir;
960 struct dirent *ent;
962 g_fscache_purge(pixmap_cache, 0);
964 path = g_strconcat(home_dir, "/.thumbnails/normal/", NULL);
966 dir = opendir(path);
967 if (!dir)
969 report_error(_("Can't delete thumbnails in %s:\n%s"),
970 path, g_strerror(errno));
971 goto out;
974 while ((ent = readdir(dir)))
976 if (ent->d_name[0] == '.')
977 continue;
978 list = g_list_prepend(list,
979 g_strconcat(path, ent->d_name, NULL));
982 closedir(dir);
984 if (list)
986 action_delete(list);
987 destroy_glist(&list);
989 else
990 info_message(_("There are no thumbnails to delete"));
991 out:
992 g_free(path);
995 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label)
997 GtkWidget *button, *align;
999 g_return_val_if_fail(option == NULL, NULL);
1001 align = gtk_alignment_new(0, 0.5, 0, 0);
1002 button = button_new_mixed(GTK_STOCK_CLEAR,
1003 _("Purge thumbnails disk cache"));
1004 gtk_container_add(GTK_CONTAINER(align), button);
1005 g_signal_connect(button, "clicked", G_CALLBACK(purge_disk_cache), NULL);
1007 return g_list_append(NULL, align);
1010 /* Exif reading.
1011 * Based on Thierry Bousch's public domain exifdump.py.
1014 #define JPEG_FORMAT 0x201
1015 #define JPEG_FORMAT_LENGTH 0x202
1018 * Extract n-byte integer in Motorola (big-endian) format
1020 static inline long long s2n_motorola(const unsigned char *p, int len)
1022 long long a=0;
1023 int i;
1025 for(i=0; i<len; i++)
1026 a=(a<<8) | (int)(p[i]);
1028 return a;
1032 * Extract n-byte integer in Intel (little-endian) format
1034 static inline long long s2n_intel(const unsigned char *p, int len)
1036 long long a=0;
1037 int i;
1039 for(i=0; i<len; i++)
1040 a=a | (((int) p[i]) << (i*8));
1042 return a;
1046 * Extract n-byte integer from data
1048 static int s2n(const unsigned char *dat, int off, int len, char format)
1050 const unsigned char *p=dat+off;
1052 switch(format) {
1053 case 'I':
1054 return s2n_intel(p, len);
1056 case 'M':
1057 return s2n_motorola(p, len);
1060 return 0;
1064 * Load header of JPEG/Exif file and attempt to extract the embedded
1065 * thumbnail. Return NULL on failure.
1067 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path)
1069 FILE *in;
1070 unsigned char header[256];
1071 int i, n;
1072 int length;
1073 unsigned char *data;
1074 char format;
1075 int ifd, entries;
1076 int thumb=0, tlength=0;
1077 GdkPixbuf *buf=NULL;
1079 in=fopen(path, "rb");
1080 if(!in) {
1081 return NULL;
1084 /* Check for Exif format */
1085 n=fread(header, 1, 12, in);
1086 if(n!=12 || strncmp((char *) header, "\377\330\377\341", 4)!=0 ||
1087 strncmp((char *)header+6, "Exif", 4)!=0) {
1088 fclose(in);
1089 return NULL;
1092 /* Read header */
1093 length=header[4]*256+header[5];
1094 data=g_new(unsigned char, length);
1095 n=fread(data, 1, length, in);
1096 fclose(in); /* File no longer needed */
1097 if(n!=length) {
1098 g_free(data);
1099 return NULL;
1102 /* Big or little endian (as 'M' or 'I') */
1103 format=data[0];
1105 /* Skip over main section */
1106 ifd=s2n(data, 4, 4, format);
1107 entries=s2n(data, ifd, 2, format);
1109 /* Second section contains data on thumbnail */
1110 ifd=s2n(data, ifd+2+12*entries, 4, format);
1111 entries=s2n(data, ifd, 2, format);
1113 /* Loop over the entries */
1114 for(i=0; i<entries; i++) {
1115 int entry=ifd+2+12*i;
1116 int tag=s2n(data, entry, 2, format);
1117 int type=s2n(data, entry+2, 2, format);
1118 int count, offset;
1120 count=s2n(data, entry+4, 4, format);
1121 offset=entry+8;
1123 if(type==4) {
1124 int val=(int) s2n(data, offset, 4, format);
1126 /* Only interested in two entries, the location of the thumbnail
1127 and its size */
1128 switch(tag) {
1129 case JPEG_FORMAT: thumb=val; break;
1130 case JPEG_FORMAT_LENGTH: tlength=val; break;
1135 if(thumb && tlength) {
1136 GError *err=NULL;
1137 GdkPixbufLoader *loader;
1139 /* Don't read outside the header (some files have incorrect data) */
1140 if(thumb+tlength>length)
1141 tlength=length-thumb;
1143 loader=gdk_pixbuf_loader_new();
1144 gdk_pixbuf_loader_write(loader, data+thumb, tlength, &err);
1145 if(err) {
1146 g_error_free(err);
1147 return NULL;
1150 gdk_pixbuf_loader_close(loader, &err);
1151 if(err) {
1152 g_error_free(err);
1153 return NULL;
1156 buf=gdk_pixbuf_loader_get_pixbuf(loader);
1157 g_object_ref(buf); /* Ref the image before we unref the loader */
1158 g_object_unref(loader);
1161 g_free(data);
1163 return buf;