Upgraded deprecated functions to new versions
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blob1d1be3c12952b2f73e88bd9aa841cccefb9664ca
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 g_object_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 pid_t child;
270 ChildThumbnail *info;
271 MIME_type *type;
272 gchar *thumb_prog;
274 image = pixmap_try_thumb(path, TRUE);
276 if (image)
278 /* Thumbnail loaded */
279 callback(data, path);
280 return;
283 /* Is it currently being created? */
284 image = g_fscache_lookup_full(pixmap_cache, path,
285 FSCACHE_LOOKUP_ONLY_NEW, &found);
287 if (found)
289 /* Thumbnail is known, or being created */
290 if (image)
291 g_object_unref(image);
292 callback(data, image? path: NULL);
293 return;
296 /* Not in memory, nor in the thumbnails directory. We need to
297 * generate it */
299 type = type_from_path(path);
300 if (!type)
301 type = text_plain;
303 /* Add an entry, set to NULL, so no-one else tries to load this
304 * image.
306 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
308 thumb_prog = thumbnail_program(type);
310 /* Only attempt to load 'images' types ourselves */
311 if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0)
313 callback(data, NULL);
314 return; /* Don't know how to handle this type */
317 child = fork();
319 if (child == -1)
321 g_free(thumb_prog);
322 delayed_error("fork(): %s", g_strerror(errno));
323 callback(data, NULL);
324 return;
327 if (child == 0)
329 /* We are the child process. (We are sloppy with freeing
330 memory, but since we go away very quickly, that's ok.) */
331 if (thumb_prog)
333 DirItem *item;
335 item = diritem_new(g_basename(thumb_prog));
337 diritem_restat(thumb_prog, item, NULL);
338 if (item->flags & ITEM_FLAG_APPDIR)
339 thumb_prog = g_strconcat(thumb_prog, "/AppRun",
340 NULL);
342 execl(thumb_prog, thumb_prog, path,
343 thumbnail_path(path),
344 g_strdup_printf("%d", PIXMAP_THUMB_SIZE),
345 NULL);
346 _exit(1);
349 child_create_thumbnail(path, type);
350 _exit(0);
353 g_free(thumb_prog);
355 info = g_new(ChildThumbnail, 1);
356 info->path = g_strdup(path);
357 info->callback = callback;
358 info->data = data;
359 on_child_death(child, (CallbackFn) thumbnail_child_done, info);
363 * Return the thumbnail for a file, only if available. If the
364 * can_load flags is set this includes loading from the cache, otherwise
365 * only if already in memory
367 MaskedPixmap *pixmap_try_thumb(const gchar *path, gboolean can_load)
369 gboolean found;
370 MaskedPixmap *image;
371 GdkPixbuf *pixbuf;
373 image = g_fscache_lookup_full(pixmap_cache, path,
374 FSCACHE_LOOKUP_ONLY_NEW, &found);
376 if (found)
378 /* Thumbnail is known, or being created */
379 if (image)
380 return image;
383 if(!can_load)
384 return NULL;
386 pixbuf = get_thumbnail_for(path);
388 if (!pixbuf)
390 struct stat info1, info2;
391 char *dir;
393 /* Skip zero-byte files. They're either empty, or
394 * special (may cause us to hang, e.g. /proc/kmsg). */
395 if (mc_stat(path, &info1) == 0 && info1.st_size == 0) {
396 return NULL;
399 dir = g_path_get_dirname(path);
401 /* If the image itself is in ~/.thumbnails, load it now
402 * (ie, don't create thumbnails for thumbnails!).
404 if (mc_stat(dir, &info1) != 0)
406 g_free(dir);
407 return NULL;
409 g_free(dir);
411 if (mc_stat(make_path(home_dir, ".thumbnails/normal"),
412 &info2) == 0 &&
413 info1.st_dev == info2.st_dev &&
414 info1.st_ino == info2.st_ino)
416 pixbuf = rox_pixbuf_new_from_file_at_scale(path,
417 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE,
418 TRUE, NULL);
419 if (!pixbuf)
421 return NULL;
426 if (pixbuf)
428 MaskedPixmap *image;
430 image = masked_pixmap_new(pixbuf);
431 g_object_unref(pixbuf);
432 g_fscache_insert(pixmap_cache, path, image, TRUE);
433 return image;
436 return NULL;
439 /****************************************************************
440 * INTERNAL FUNCTIONS *
441 ****************************************************************/
443 /* Create a thumbnail file for this image */
444 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
446 struct stat info;
447 gchar *path;
448 int original_width, original_height;
449 GString *to;
450 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
451 mode_t old_mask;
452 int name_len;
453 GdkPixbuf *thumb;
455 thumb = scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
457 original_width = gdk_pixbuf_get_width(full);
458 original_height = gdk_pixbuf_get_height(full);
460 if (mc_stat(pathname, &info) != 0)
461 return;
463 swidth = g_strdup_printf("%d", original_width);
464 sheight = g_strdup_printf("%d", original_height);
465 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
466 smtime = g_strdup_printf("%ld", (long) info.st_mtime);
468 path = pathdup(pathname);
469 uri = g_filename_to_uri(path, NULL, NULL);
470 if (!uri)
471 uri = g_strconcat("file://", path, NULL);
472 md5 = md5_hash(uri);
473 g_free(path);
475 to = g_string_new(home_dir);
476 g_string_append(to, "/.thumbnails");
477 mkdir(to->str, 0700);
478 g_string_append(to, "/normal/");
479 mkdir(to->str, 0700);
480 g_string_append(to, md5);
481 name_len = to->len + 4; /* Truncate to this length when renaming */
482 g_string_append_printf(to, ".png.ROX-Filer-%ld", (long) getpid());
484 g_free(md5);
486 old_mask = umask(0077);
487 gdk_pixbuf_save(thumb, to->str, "png", NULL,
488 "tEXt::Thumb::Image::Width", swidth,
489 "tEXt::Thumb::Image::Height", sheight,
490 "tEXt::Thumb::Size", ssize,
491 "tEXt::Thumb::MTime", smtime,
492 "tEXt::Thumb::URI", uri,
493 "tEXt::Software", PROJECT,
494 NULL);
495 umask(old_mask);
497 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
498 * a race condition if two programs create the same thumb at
499 * once.
502 gchar *final;
504 final = g_strndup(to->str, name_len);
505 if (rename(to->str, final))
506 g_warning("Failed to rename '%s' to '%s': %s",
507 to->str, final, g_strerror(errno));
508 g_free(final);
511 g_string_free(to, TRUE);
512 g_free(swidth);
513 g_free(sheight);
514 g_free(ssize);
515 g_free(smtime);
516 g_free(uri);
519 static gchar *thumbnail_path(const char *path)
521 gchar *uri, *md5;
522 GString *to;
523 gchar *ans;
525 uri = g_filename_to_uri(path, NULL, NULL);
526 if(!uri)
527 uri = g_strconcat("file://", path, NULL);
528 md5 = md5_hash(uri);
530 to = g_string_new(home_dir);
531 g_string_append(to, "/.thumbnails");
532 mkdir(to->str, 0700);
533 g_string_append(to, "/normal/");
534 mkdir(to->str, 0700);
535 g_string_append(to, md5);
536 g_string_append(to, ".png");
538 g_free(md5);
539 g_free(uri);
541 ans=to->str;
542 g_string_free(to, FALSE);
544 return ans;
547 /* Return a program to create thumbnails for files of this type.
548 * NULL to try to make it ourself (using gdk).
549 * g_free the result.
551 static gchar *thumbnail_program(MIME_type *type)
553 gchar *leaf;
554 gchar *path;
556 if (!type)
557 return NULL;
559 leaf = g_strconcat(type->media_type, "_", type->subtype, NULL);
560 path = choices_find_xdg_path_load(leaf, "MIME-thumb", SITE);
561 g_free(leaf);
562 if (path)
564 return path;
567 path = choices_find_xdg_path_load(type->media_type, "MIME-thumb",
568 SITE);
570 return path;
573 /* Called in a subprocess. Load path and create the thumbnail
574 * file. Parent will notice when we die.
576 static void child_create_thumbnail(const gchar *path, MIME_type *type)
578 GdkPixbuf *image=NULL;
580 if(strcmp(type->subtype, "jpeg")==0)
581 image=extract_tiff_thumbnail(path);
583 if(!image)
584 image = rox_pixbuf_new_from_file_at_scale(path,
585 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
587 if (image)
588 save_thumbnail(path, image);
590 /* (no need to unref, as we're about to exit) */
593 /* Called when the child process exits */
594 static void thumbnail_child_done(ChildThumbnail *info)
596 GdkPixbuf *thumb;
598 thumb = get_thumbnail_for(info->path);
600 if (thumb)
602 MaskedPixmap *image;
604 image = masked_pixmap_new(thumb);
605 g_object_unref(thumb);
607 g_fscache_insert(pixmap_cache, info->path, image, FALSE);
608 g_object_unref(image);
610 info->callback(info->data, info->path);
612 else
613 info->callback(info->data, NULL);
615 g_free(info->path);
616 g_free(info);
619 /* Check if we have an up-to-date thumbnail for this image.
620 * If so, return it. Otherwise, returns NULL.
622 static GdkPixbuf *get_thumbnail_for(const char *pathname)
624 GdkPixbuf *thumb = NULL;
625 char *thumb_path, *md5, *uri, *path;
626 const char *ssize, *smtime;
627 struct stat info;
628 time_t ttime, now;
630 path = pathdup(pathname);
631 uri = g_filename_to_uri(path, NULL, NULL);
632 if(!uri)
633 uri = g_strconcat("file://", path, NULL);
634 md5 = md5_hash(uri);
635 g_free(uri);
637 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
638 home_dir, md5);
639 g_free(md5);
641 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
642 if (!thumb)
643 goto err;
645 /* Note that these don't need freeing... */
646 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
647 /* This is optional, so don't flag an error if it is missing */
649 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
650 if (!smtime)
651 goto err;
653 if (mc_stat(path, &info) != 0)
654 goto err;
656 ttime=(time_t) atol(smtime);
657 time(&now);
658 if (info.st_mtime != ttime && now>ttime+PIXMAP_THUMB_TOO_OLD_TIME)
659 goto err;
661 if (ssize && info.st_size < atol(ssize))
662 goto err;
664 goto out;
665 err:
666 if (thumb)
667 g_object_unref(thumb);
668 thumb = NULL;
669 out:
670 g_free(path);
671 g_free(thumb_path);
672 return thumb;
675 /* Load the image 'path' and return a pointer to the resulting
676 * MaskedPixmap. NULL on failure.
677 * Doesn't check for thumbnails (this is for small icons).
679 static MaskedPixmap *image_from_file(const char *path)
681 GdkPixbuf *pixbuf;
682 MaskedPixmap *image;
683 GError *error = NULL;
685 pixbuf = gdk_pixbuf_new_from_file(path, &error);
686 if (!pixbuf)
688 g_warning("%s\n", error->message);
689 g_error_free(error);
690 return NULL;
693 image = masked_pixmap_new(pixbuf);
695 g_object_unref(pixbuf);
697 return image;
700 /* Load this icon named by this .desktop file from the current theme.
701 * NULL on failure.
703 static MaskedPixmap *image_from_desktop_file(const char *path)
705 GError *error = NULL;
706 MaskedPixmap *image = NULL;
707 char *icon = NULL;
709 icon = get_value_from_desktop_file(path,
710 "Desktop Entry", "Icon", &error);
711 if (error)
713 g_warning("Failed to parse .desktop file '%s':\n%s",
714 path, error->message);
715 goto err;
717 if (!icon)
718 goto err;
720 if (icon[0] == '/')
721 image = image_from_file(icon);
722 else
724 GdkPixbuf *pixbuf;
725 int tmp_fd;
726 char *extension;
728 /* For some unknown reason, some icon names have extensions.
729 * Remove them.
731 extension = strrchr(icon, '.');
732 if (extension && (strcmp(extension, ".png") == 0
733 || strcmp(extension, ".xpm") == 0
734 || strcmp(extension, ".svg") == 0))
736 *extension = '\0';
739 /* SVG reader is very noisy, so redirect stderr to stdout */
740 tmp_fd = dup(2);
741 dup2(1, 2);
742 pixbuf = theme_load_icon(icon, HUGE_WIDTH, 0, NULL);
743 dup2(tmp_fd, 2);
744 close(tmp_fd);
746 if (pixbuf == NULL)
747 goto err; /* Might just not be in the theme */
749 image = masked_pixmap_new(pixbuf);
750 g_object_unref(pixbuf);
752 err:
753 if (error != NULL)
754 g_error_free(error);
755 if (icon != NULL)
756 g_free(icon);
757 return image;
760 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
761 * If src is small enough, then ref it and return that.
763 GdkPixbuf *scale_pixbuf(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 <= max_w && h <= max_h)
772 g_object_ref(src);
773 return src;
775 else
777 float scale_x = ((float) w) / max_w;
778 float scale_y = ((float) h) / max_h;
779 float scale = MAX(scale_x, scale_y);
780 int dest_w = w / scale;
781 int dest_h = h / scale;
783 return gdk_pixbuf_scale_simple(src,
784 MAX(dest_w, 1),
785 MAX(dest_h, 1),
786 GDK_INTERP_BILINEAR);
790 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
791 * If src is that size or bigger, then ref it and return that.
793 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
795 int w, h;
797 w = gdk_pixbuf_get_width(src);
798 h = gdk_pixbuf_get_height(src);
800 if (w == 0 || h == 0 || w >= max_w || h >= max_h)
802 g_object_ref(src);
803 return src;
805 else
807 float scale_x = max_w / ((float) w);
808 float scale_y = max_h / ((float) h);
809 float scale = MIN(scale_x, scale_y);
811 return gdk_pixbuf_scale_simple(src,
812 w * scale,
813 h * scale,
814 GDK_INTERP_BILINEAR);
818 /* Return a pointer to the (static) bad image. The ref counter will ensure
819 * that the image is never freed.
821 static MaskedPixmap *get_bad_image(void)
823 GdkPixbuf *bad;
824 MaskedPixmap *mp;
826 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
827 mp = masked_pixmap_new(bad);
828 g_object_unref(bad);
830 return mp;
833 /* Called now and then to clear out old pixmaps */
834 static gint purge(gpointer data)
836 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
838 return TRUE;
841 static gpointer parent_class;
843 static void masked_pixmap_finialize(GObject *object)
845 MaskedPixmap *mp = (MaskedPixmap *) object;
847 if (mp->src_pixbuf)
849 g_object_unref(mp->src_pixbuf);
850 mp->src_pixbuf = NULL;
853 if (mp->huge_pixbuf)
855 g_object_unref(mp->huge_pixbuf);
856 mp->huge_pixbuf = NULL;
858 if (mp->pixbuf)
860 g_object_unref(mp->pixbuf);
861 mp->pixbuf = NULL;
864 if (mp->sm_pixbuf)
866 g_object_unref(mp->sm_pixbuf);
867 mp->sm_pixbuf = NULL;
870 G_OBJECT_CLASS(parent_class)->finalize(object);
873 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
875 GObjectClass *object = (GObjectClass *) gclass;
877 parent_class = g_type_class_peek_parent(gclass);
879 object->finalize = masked_pixmap_finialize;
882 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
884 MaskedPixmap *mp = (MaskedPixmap *) object;
886 mp->src_pixbuf = NULL;
888 mp->huge_pixbuf = NULL;
889 mp->huge_width = -1;
890 mp->huge_height = -1;
892 mp->pixbuf = NULL;
893 mp->width = -1;
894 mp->height = -1;
896 mp->sm_pixbuf = NULL;
897 mp->sm_width = -1;
898 mp->sm_height = -1;
901 static GType masked_pixmap_get_type(void)
903 static GType type = 0;
905 if (!type)
907 static const GTypeInfo info =
909 sizeof (MaskedPixmapClass),
910 NULL, /* base_init */
911 NULL, /* base_finalise */
912 masked_pixmap_class_init,
913 NULL, /* class_finalise */
914 NULL, /* class_data */
915 sizeof(MaskedPixmap),
916 0, /* n_preallocs */
917 masked_pixmap_init
920 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
921 &info, 0);
924 return type;
927 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
929 MaskedPixmap *mp;
930 GdkPixbuf *src_pixbuf, *normal_pixbuf;
932 g_return_val_if_fail(full_size != NULL, NULL);
934 src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
935 g_return_val_if_fail(src_pixbuf != NULL, NULL);
937 normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
938 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
940 mp = g_object_new(masked_pixmap_get_type(), NULL);
942 mp->src_pixbuf = src_pixbuf;
944 mp->pixbuf = normal_pixbuf;
945 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
946 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
948 return mp;
951 /* Load all the standard pixmaps. Also sets the default window icon. */
952 static void load_default_pixmaps(void)
954 GdkPixbuf *pixbuf;
955 GError *error = NULL;
957 im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING,
958 GTK_ICON_SIZE_DIALOG);
959 im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION,
960 GTK_ICON_SIZE_DIALOG);
962 im_dirs = load_pixmap("dirs");
963 im_appdir = load_pixmap("application");
965 pixbuf = gdk_pixbuf_new_from_file(
966 make_path(app_dir, ".DirIcon"), &error);
967 if (pixbuf)
969 GList *icon_list;
971 icon_list = g_list_append(NULL, pixbuf);
972 gtk_window_set_default_icon_list(icon_list);
973 g_list_free(icon_list);
975 g_object_unref(G_OBJECT(pixbuf));
977 else
979 g_warning("%s\n", error->message);
980 g_error_free(error);
984 /* Also purges memory cache */
985 static void purge_disk_cache(GtkWidget *button, gpointer data)
987 char *path;
988 GList *list = NULL;
989 DIR *dir;
990 struct dirent *ent;
992 g_fscache_purge(pixmap_cache, 0);
994 path = g_strconcat(home_dir, "/.thumbnails/normal/", NULL);
996 dir = opendir(path);
997 if (!dir)
999 report_error(_("Can't delete thumbnails in %s:\n%s"),
1000 path, g_strerror(errno));
1001 goto out;
1004 while ((ent = readdir(dir)))
1006 if (ent->d_name[0] == '.')
1007 continue;
1008 list = g_list_prepend(list,
1009 g_strconcat(path, ent->d_name, NULL));
1012 closedir(dir);
1014 if (list)
1016 action_delete(list);
1017 destroy_glist(&list);
1019 else
1020 info_message(_("There are no thumbnails to delete"));
1021 out:
1022 g_free(path);
1025 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label)
1027 GtkWidget *button, *align;
1029 g_return_val_if_fail(option == NULL, NULL);
1031 align = gtk_alignment_new(0, 0.5, 0, 0);
1032 button = button_new_mixed(GTK_STOCK_CLEAR,
1033 _("Purge thumbnails disk cache"));
1034 gtk_container_add(GTK_CONTAINER(align), button);
1035 g_signal_connect(button, "clicked", G_CALLBACK(purge_disk_cache), NULL);
1037 return g_list_append(NULL, align);
1040 /* Exif reading.
1041 * Based on Thierry Bousch's public domain exifdump.py.
1044 #define JPEG_FORMAT 0x201
1045 #define JPEG_FORMAT_LENGTH 0x202
1048 * Extract n-byte integer in Motorola (big-endian) format
1050 static inline long long s2n_motorola(const unsigned char *p, int len)
1052 long long a=0;
1053 int i;
1055 for(i=0; i<len; i++)
1056 a=(a<<8) | (int)(p[i]);
1058 return a;
1062 * Extract n-byte integer in Intel (little-endian) format
1064 static inline long long s2n_intel(const unsigned char *p, int len)
1066 long long a=0;
1067 int i;
1069 for(i=0; i<len; i++)
1070 a=a | (((int) p[i]) << (i*8));
1072 return a;
1076 * Extract n-byte integer from data
1078 static int s2n(const unsigned char *dat, int off, int len, char format)
1080 const unsigned char *p=dat+off;
1082 switch(format) {
1083 case 'I':
1084 return s2n_intel(p, len);
1086 case 'M':
1087 return s2n_motorola(p, len);
1090 return 0;
1094 * Load header of JPEG/Exif file and attempt to extract the embedded
1095 * thumbnail. Return NULL on failure.
1097 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path)
1099 FILE *in;
1100 unsigned char header[256];
1101 int i, n;
1102 int length;
1103 unsigned char *data;
1104 char format;
1105 int ifd, entries;
1106 int thumb=0, tlength=0;
1107 GdkPixbuf *buf=NULL;
1109 in=fopen(path, "rb");
1110 if(!in) {
1111 return NULL;
1114 /* Check for Exif format */
1115 n=fread(header, 1, 12, in);
1116 if(n!=12 || strncmp((char *) header, "\377\330\377\341", 4)!=0 ||
1117 strncmp((char *)header+6, "Exif", 4)!=0) {
1118 fclose(in);
1119 return NULL;
1122 /* Read header */
1123 length=header[4]*256+header[5];
1124 data=g_new(unsigned char, length);
1125 n=fread(data, 1, length, in);
1126 fclose(in); /* File no longer needed */
1127 if(n!=length) {
1128 g_free(data);
1129 return NULL;
1132 /* Big or little endian (as 'M' or 'I') */
1133 format=data[0];
1135 /* Skip over main section */
1136 ifd=s2n(data, 4, 4, format);
1137 entries=s2n(data, ifd, 2, format);
1139 /* Second section contains data on thumbnail */
1140 ifd=s2n(data, ifd+2+12*entries, 4, format);
1141 entries=s2n(data, ifd, 2, format);
1143 /* Loop over the entries */
1144 for(i=0; i<entries; i++) {
1145 int entry=ifd+2+12*i;
1146 int tag=s2n(data, entry, 2, format);
1147 int type=s2n(data, entry+2, 2, format);
1148 int count, offset;
1150 count=s2n(data, entry+4, 4, format);
1151 offset=entry+8;
1153 if(type==4) {
1154 int val=(int) s2n(data, offset, 4, format);
1156 /* Only interested in two entries, the location of the thumbnail
1157 and its size */
1158 switch(tag) {
1159 case JPEG_FORMAT: thumb=val; break;
1160 case JPEG_FORMAT_LENGTH: tlength=val; break;
1165 if(thumb && tlength) {
1166 GError *err=NULL;
1167 GdkPixbufLoader *loader;
1169 /* Don't read outside the header (some files have incorrect data) */
1170 if(thumb+tlength>length)
1171 tlength=length-thumb;
1173 loader=gdk_pixbuf_loader_new();
1174 gdk_pixbuf_loader_write(loader, data+thumb, tlength, &err);
1175 if(err) {
1176 g_error_free(err);
1177 return NULL;
1180 gdk_pixbuf_loader_close(loader, &err);
1181 if(err) {
1182 g_error_free(err);
1183 return NULL;
1186 buf=gdk_pixbuf_loader_get_pixbuf(loader);
1187 g_object_ref(buf); /* Ref the image before we unref the loader */
1188 g_object_unref(loader);
1191 g_free(data);
1193 return buf;