changeset 37:67ba4381497e

Wed Apr 13 18:16:14 2005 John Ellis <johne@verizon.net> * cache-loader.[ch]: New utility to load cache-able data. * cache.[ch]: Add embedded (exif) date caching. * pan-view.c: Use new cache loading mechanism. Add exif date support to timeline and calendar view. * src/Makefile.am: Add cache-loader.[c,h]. ##### Note: GQview CVS on sourceforge is not always up to date, please use ##### ##### an offical release when making enhancements and translation updates. #####
author gqview
date Wed, 13 Apr 2005 22:29:53 +0000
parents 9b01fe7e84d5
children d5f8f8a4d47b
files ChangeLog TODO src/Makefile.am src/cache-loader.c src/cache-loader.h src/cache.c src/cache.h src/pan-view.c
diffstat 8 files changed, 522 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Apr 12 12:11:31 2005 +0000
+++ b/ChangeLog	Wed Apr 13 22:29:53 2005 +0000
@@ -1,3 +1,11 @@
+Wed Apr 13 18:16:14 2005  John Ellis  <johne@verizon.net>
+
+	* cache-loader.[ch]: New utility to load cache-able data.
+	* cache.[ch]: Add embedded (exif) date caching.
+	* pan-view.c: Use new cache loading mechanism. Add exif date support
+	to timeline and calendar view.
+	* src/Makefile.am: Add cache-loader.[c,h].
+
 Tue Apr 12 07:59:20 2005  John Ellis  <johne@verizon.net>
 
 	* pan-view.c: Use mostly neutral (gray) colors in the pan view to avoid
--- a/TODO	Tue Apr 12 12:11:31 2005 +0000
+++ b/TODO	Wed Apr 13 22:29:53 2005 +0000
@@ -5,31 +5,22 @@
 ----------------------------------------------
 
  > pixbuf-renderer.c:
-  d> fix two pass render from corrupting it->qd pointer (need one pointer for each queue?).
-  d> fix image_change_from_image (to do this need a pixbuf_renderer_move_image).
-  d> fix broken zoom out drawing when using source tiles.
-  d> fix 2pass zoom when using source tiles and zoomed out (not always rendering second pass)
-  d> pixbuf_renderer_move should be clearing the tiles of the source image since they are no longer
-     valid and are then wasting memory (for example when full screen is active).
+   > tile dispose order is slightly incorrect, furthest ones from current position should be dropped first
+
 
  > image.c:
-  d> need to keep a list of ImageWindows and provide function to sync options to each object.
   d> test and probably fix delay_flip.
       > UPDATE: works as before (pre pixbuf-renderer), but should be fixed to provide a single redraw
         by pre-rendering any scaled tiles that are visible before signaling 'render_complete'.
    > make this a g_object with signals for completed, changed, etc.
-  d> fix region computation rounding when updating scaled image in 'area ready' signal.
    > fix delay flip 'completed' signal (full screen busy cursor is not always turned off)
-  d> fix slow loading of images when zoomed out - bug could be poor clamping to visible in PixbufRenderer.
+
+ > cache-load.c:
+   > should honor enable_thumbnails setting
 
 
  > work on pan view:
    > Pick a better keyboard shortcut than Control + J :)
-  d> Fix occasional redraw bugs when zoomed out.
-  d> Fix occasional odd requests for non-visible tiles when zoomed out (related to above?).
-  d> Fix slowness in image.c with huge grid size by changing use of pre-allocated tile array
-     to on-demand tile allocation (can this be implemented like source tiles?).
-     OR: use an array so that we do not need to walk a GList of pre-allocated tile containers
   w> Fix search scrolling to try to center image and info popup.
    > Fix info popup location to opposing horizontal side when near edge of grid.
    > Find something to do with middle mouse clicks.
@@ -37,12 +28,16 @@
    > search should highlight all matching images (with toggle?)
    > should non-thumbnail images have a drop shadow?
   d> optimize pixbuf_draw_line (line endpoints should clip to draw region before draw loop)
+     > introduced bug (looks like clamp or rounding error)
   d> optimize pixbuf_draw_triangle
+     > introduced bug (round error?)
    > does new pixbuf_draw_triangle contain line edge rounding error?
-  d> move pixbuf_draw_* stuff into pixbuf_util.c
+   > check ref counting of image when redrawing after finish loading
+   > speed up sorting image.. stage when sorting/merging cache list
+
 
    > time line view:
-     > allow use of file date or EXIF (embedded) date.
+    w> allow use of file date or EXIF (embedded) date.
      > allow horizontal _or_ vertical orientation.
 
    > calendar view:
--- a/src/Makefile.am	Tue Apr 12 12:11:31 2005 +0000
+++ b/src/Makefile.am	Wed Apr 13 22:29:53 2005 +0000
@@ -60,6 +60,8 @@
 	bar_sort.h	\
 	cache.c		\
 	cache.h		\
+	cache-loader.c	\
+	cache-loader.h	\
 	cache_maint.c	\
 	cache_maint.h	\
 	cellrenderericon.c	\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cache-loader.c	Wed Apr 13 22:29:53 2005 +0000
@@ -0,0 +1,246 @@
+/*
+ * GQview
+ * (C) 2005 John Ellis
+ *
+ * Author: John Ellis
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+#include "gqview.h"
+#include "cache-loader.h"
+
+#include "exif.h"
+#include "md5-util.h"
+#include "ui_fileops.h"
+
+
+static gboolean cache_loader_process(CacheLoader *cl);
+
+
+static void cache_loader_done_cb(ImageLoader *il, gpointer data)
+{
+	CacheLoader *cl = data;
+
+	cache_loader_process(cl);
+}
+
+static void cache_loader_error_cb(ImageLoader *il, gpointer data)
+{
+	CacheLoader *cl = data;
+
+	cl->error = TRUE;
+	cache_loader_done_cb(il, data);
+}
+
+static gboolean cache_loader_process(CacheLoader *cl)
+{
+	if (cl->todo_mask & CACHE_LOADER_SIMILARITY &&
+	    !cl->cd->similarity)
+		{
+		GdkPixbuf *pixbuf;
+
+		if (!cl->il && !cl->error)
+			{
+			cl->il = image_loader_new(cl->path);
+			image_loader_set_error_func(cl->il, cache_loader_error_cb, cl);
+			if (image_loader_start(cl->il, cache_loader_done_cb, cl))
+				{
+				return FALSE;
+				}
+
+			cl->error = TRUE;
+			}
+
+		pixbuf = image_loader_get_pixbuf(cl->il);
+		if (pixbuf)
+			{
+			if (!cl->error)
+				{
+				ImageSimilarityData *sim;
+
+				sim = image_sim_new_from_pixbuf(pixbuf);
+				cache_sim_data_set_similarity(cl->cd, sim);
+				image_sim_free(sim);
+
+				cl->done_mask |= CACHE_LOADER_SIMILARITY;
+				}
+
+			/* we have the dimensions via pixbuf */
+			if (!cl->cd->dimensions)
+				{
+				cache_sim_data_set_dimensions(cl->cd, gdk_pixbuf_get_width(pixbuf),
+								      gdk_pixbuf_get_height(pixbuf));
+				if (cl->todo_mask & CACHE_LOADER_DIMENSIONS)
+					{
+					cl->todo_mask &= ~CACHE_LOADER_DIMENSIONS;
+					cl->done_mask |= CACHE_LOADER_DIMENSIONS;
+					}
+				}
+			}
+
+		image_loader_free(cl->il);
+		cl->il = NULL;
+
+		cl->todo_mask &= ~CACHE_LOADER_SIMILARITY;
+		}
+	else if (cl->todo_mask & CACHE_LOADER_DIMENSIONS &&
+		 !cl->cd->dimensions)
+		{
+		if (!cl->error &&
+		    image_load_dimensions(cl->path, &cl->cd->width, &cl->cd->height))
+			{
+			cl->cd->dimensions = TRUE;
+			cl->done_mask |= CACHE_LOADER_DIMENSIONS;
+			}
+		else
+			{
+			cl->error = TRUE;
+			}
+
+		cl->todo_mask &= ~CACHE_LOADER_DIMENSIONS;
+		}
+	else if (cl->todo_mask & CACHE_LOADER_MD5SUM &&
+		 !cl->cd->have_md5sum)
+		{
+		if (md5_get_digest_from_file_utf8(cl->path, cl->cd->md5sum))
+			{
+			cl->cd->have_md5sum = TRUE;
+			cl->done_mask |= CACHE_LOADER_MD5SUM;
+			}
+		else
+			{
+			cl->error = TRUE;
+			}
+
+		cl->todo_mask &= ~CACHE_LOADER_MD5SUM;
+		}
+	else if (cl->todo_mask & CACHE_LOADER_DATE &&
+		 !cl->cd->have_date)
+		{
+		time_t date = -1;
+		ExifData *exif;
+
+		exif = exif_read(cl->path);
+		if (exif)
+			{
+			gchar *text;
+
+			text = exif_get_data_as_text(exif, "fDateTime");
+			if (text)
+				{
+				struct tm t = { 0 };
+
+				if (sscanf(text, "%d:%d:%d %d:%d:%d", &t.tm_year, &t.tm_mon, &t.tm_mday,
+					   &t.tm_hour, &t.tm_min, &t.tm_sec) == 6)
+					{
+					t.tm_year -= 1900;
+					t.tm_mon -= 1;
+					date = mktime(&t);
+					}
+				g_free(text);
+				}
+			exif_free(exif);
+			}
+
+		cl->cd->date = date;
+		cl->cd->have_date = TRUE;
+
+		cl->done_mask |= CACHE_LOADER_DATE;
+		cl->todo_mask &= ~CACHE_LOADER_DATE;
+		}
+	else
+		{
+		/* done, save then call done function */
+		if (enable_thumb_caching &&
+		    cl->done_mask != CACHE_LOADER_NONE)
+			{
+			gchar *base;
+			mode_t mode = 0755;
+
+			base = cache_get_location(CACHE_TYPE_SIM, cl->path, FALSE, &mode);
+			if (cache_ensure_dir_exists(base, mode))
+				{
+				g_free(cl->cd->path);
+				cl->cd->path = cache_get_location(CACHE_TYPE_SIM, cl->path, TRUE, NULL);
+				if (cache_sim_data_save(cl->cd))
+					{
+					filetime_set(cl->cd->path, filetime(cl->path));
+					}
+				}
+			g_free(base);
+			}
+
+		cl->idle_id = -1;
+
+		if (cl->done_func)
+			{
+			cl->done_func(cl, cl->error, cl->done_data);
+			}
+
+		return FALSE;
+		}
+
+	return TRUE;
+}
+
+static gboolean cache_loader_idle_cb(gpointer data)
+{
+	CacheLoader *cl = data;
+
+	return cache_loader_process(cl);
+}
+
+CacheLoader *cache_loader_new(const gchar *path, CacheDataType load_mask,
+			      CacheLoaderDoneFunc done_func, gpointer done_data)
+{
+	CacheLoader *cl;
+	gchar *found;
+
+	if (!path || !isfile(path)) return NULL;
+
+	cl = g_new0(CacheLoader, 1);
+	cl->path = g_strdup(path);
+
+	cl->done_func = done_func;
+	cl->done_data = done_data;
+
+	found = cache_find_location(CACHE_TYPE_SIM, path);
+	if (found && filetime(found) == filetime(path))
+		{
+		cl->cd = cache_sim_data_load(found);
+		}
+	g_free(found);
+
+	if (!cl->cd) cl->cd = cache_sim_data_new();
+
+	cl->todo_mask = load_mask;
+	cl->done_mask = CACHE_LOADER_NONE;
+
+	cl->il = NULL;
+	cl->idle_id = g_idle_add(cache_loader_idle_cb, cl);
+
+	cl->error = FALSE;
+
+	return cl;
+}
+
+void cache_loader_free(CacheLoader *cl)
+{
+	if (!cl) return;
+
+	if (cl->idle_id != -1)
+		{
+		g_source_remove(cl->idle_id);
+		cl->idle_id = -1;
+		}
+
+	image_loader_free(cl->il);
+	cache_sim_data_free(cl->cd);
+
+	g_free(cl->path);
+	g_free(cl);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cache-loader.h	Wed Apr 13 22:29:53 2005 +0000
@@ -0,0 +1,59 @@
+/*
+ * GQview
+ * (C) 2005 John Ellis
+ *
+ * Author: John Ellis
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+
+#ifndef CACHE_LOADER_H
+#define CACHE_LOADER_H
+
+
+#include "cache.h"
+#include "image-load.h"
+
+
+typedef struct _CacheLoader CacheLoader;
+
+typedef void (* CacheLoaderDoneFunc)(CacheLoader *cl, gint error, gpointer data);
+
+
+typedef enum {
+	CACHE_LOADER_NONE	= 0,
+	CACHE_LOADER_DIMENSIONS	= 1 << 0,
+	CACHE_LOADER_DATE	= 1 << 1,
+	CACHE_LOADER_MD5SUM	= 1 << 2,
+	CACHE_LOADER_SIMILARITY	= 1 << 3
+} CacheDataType;
+
+struct _CacheLoader {
+	gchar *path;
+	CacheData *cd;
+
+	CacheDataType todo_mask;
+	CacheDataType done_mask;
+
+	CacheLoaderDoneFunc done_func;
+	gpointer done_data;
+
+	gint error;
+
+	ImageLoader *il;
+	gint idle_id;
+};
+
+
+CacheLoader *cache_loader_new(const gchar *path, CacheDataType load_mask,
+			      CacheLoaderDoneFunc done_func, gpointer done_data);
+
+void cache_loader_free(CacheLoader *cl);
+
+
+#endif
+
+
--- a/src/cache.c	Tue Apr 12 12:11:31 2005 +0000
+++ b/src/cache.c	Wed Apr 13 22:29:53 2005 +0000
@@ -27,6 +27,7 @@
  * SIMcache
  * #coment
  * Dimensions=[<width> x <height>]
+ * Date=[<value in time_t format, or -1 if no embedded date>]
  * Checksum=[<value>]
  * MD5sum=[<32 character ascii text digest>]
  * SimilarityGrid[32 x 32]=<3072 bytes of data (1024 pixels in RGB format, 1 pixel is 24bits)>
@@ -51,8 +52,7 @@
 	CacheData *cd;
 
 	cd = g_new0(CacheData, 1);
-	cd->dimensions = FALSE;
-	cd->similarity = FALSE;
+	cd->date = -1;
 
 	return cd;
 }
@@ -81,6 +81,15 @@
 	return TRUE;
 }
 
+static gint cache_sim_write_date(FILE *f, CacheData *cd)
+{
+	if (!f || !cd || !cd->have_date) return FALSE;
+
+	fprintf(f, "Date=[%ld]\n", cd->date);
+
+	return TRUE;
+}
+
 static gint cache_sim_write_checksum(FILE *f, CacheData *cd)
 {
 	if (!f || !cd || !cd->have_checksum) return FALSE;
@@ -157,6 +166,7 @@
 
 	fprintf(f, "SIMcache\n#%s %s\n", PACKAGE, VERSION);
 	cache_sim_write_dimensions(f, cd);
+	cache_sim_write_date(f, cd);
 	cache_sim_write_checksum(f, cd);
 	cache_sim_write_md5sum(f, cd);
 	cache_sim_write_similarity(f, cd);
@@ -234,6 +244,46 @@
 	return FALSE;
 }
 
+static gint cache_sim_read_date(FILE *f, char *buf, int s, CacheData *cd)
+{
+	if (!f || !buf || !cd) return FALSE;
+
+	if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
+
+	if (fseek(f, - s, SEEK_CUR) == 0)
+		{
+		char b;
+		char buf[1024];
+		gint p = 0;
+
+		b = 'X';
+		while (b != '[')
+			{
+			if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
+			}
+		while (b != ']' && p < 1023)
+			{
+			if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
+			buf[p] = b;
+			p++;
+			}
+
+		while (b != '\n')
+			{
+			if (fread(&b, sizeof(b), 1, f) != 1) break;
+			}
+
+		buf[p] = '\0';
+		cd->date = strtol(buf, NULL, 10);
+
+		cd->have_date = TRUE;
+
+		return TRUE;
+		}
+
+	return FALSE;
+}
+
 static gint cache_sim_read_checksum(FILE *f, char *buf, int s, CacheData *cd)
 {
 	if (!f || !buf || !cd) return FALSE;
@@ -414,6 +464,7 @@
 			{
 			if (!cache_sim_read_comment(f, buf, s, cd) &&
 			    !cache_sim_read_dimensions(f, buf, s, cd) &&
+			    !cache_sim_read_date(f, buf, s, cd) &&
 			    !cache_sim_read_checksum(f, buf, s, cd) &&
 			    !cache_sim_read_md5sum(f, buf, s, cd) &&
 			    !cache_sim_read_similarity(f, buf, s, cd))
@@ -449,6 +500,14 @@
 	cd->dimensions = TRUE;
 }
 
+void cache_sim_data_set_date(CacheData *cd, time_t date)
+{
+	if (!cd) return;
+
+	cd->date = date;
+	cd->have_date = TRUE;
+}
+
 void cache_sim_data_set_checksum(CacheData *cd, long checksum)
 {
 	if (!cd) return;
--- a/src/cache.h	Tue Apr 12 12:11:31 2005 +0000
+++ b/src/cache.h	Wed Apr 13 22:29:53 2005 +0000
@@ -40,11 +40,13 @@
 	gchar *path;
 	gint width;
 	gint height;
+	time_t date;
 	long checksum;
 	guchar md5sum[16];
 	ImageSimilarityData *sim;
 
 	gint dimensions;
+	gint have_date;
 	gint have_checksum;
 	gint have_md5sum;
 	gint similarity;
@@ -60,6 +62,7 @@
 CacheData *cache_sim_data_load(const gchar *path);
 
 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h);
+void cache_sim_data_set_date(CacheData *cd, time_t date);
 void cache_sim_data_set_checksum(CacheData *cd, long checksum);
 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16]);
 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd);
--- a/src/pan-view.c	Tue Apr 12 12:11:31 2005 +0000
+++ b/src/pan-view.c	Wed Apr 13 22:29:53 2005 +0000
@@ -14,6 +14,7 @@
 #include "pan-view.h"
 
 #include "cache.h"
+#include "cache-loader.h"
 #include "dnd.h"
 #include "editors.h"
 #include "filelist.h"
@@ -125,6 +126,8 @@
 #define PAN_PREF_GROUP "pan_view_options"
 #define PAN_PREF_HIDE_WARNING "hide_performance_warning"
 
+#define SORT_BY_EXIF_DATE 1
+
 
 typedef enum {
 	LAYOUT_TIMELINE = 0,
@@ -244,6 +247,7 @@
 	gint cache_count;
 	gint cache_total;
 	gint cache_tick;
+	CacheLoader *cache_cl;
 
 	ImageLoader *il;
 	ThumbLoader *tl;
@@ -279,6 +283,8 @@
 
 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height);
 
+static void pan_window_layout_update_idle(PanWindow *pw);
+
 static GtkWidget *pan_popup_menu(PanWindow *pw);
 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off);
 
@@ -432,6 +438,9 @@
 	pw->cache_count = 0;
 	pw->cache_total = 0;
 	pw->cache_tick = 0;
+
+	cache_loader_free(pw->cache_cl);
+	pw->cache_cl = NULL;
 }
 
 static void pan_cache_fill(PanWindow *pw, const gchar *path)
@@ -446,17 +455,40 @@
 	pw->cache_total = g_list_length(pw->cache_todo);
 }
 
+static void pan_cache_step_done_cb(CacheLoader *cl, gint error, gpointer data)
+{
+	PanWindow *pw = data;
+
+	if (pw->cache_list)
+		{
+		PanCacheData *pc;
+		pc = pw->cache_list->data;
+
+		if (!pc->cd)
+			{
+			pc->cd = cl->cd;
+			cl->cd = NULL;
+			}
+		}
+
+	cache_loader_free(cl);
+	pw->cache_cl = NULL;
+
+	pan_window_layout_update_idle(pw);
+}
+
 static gint pan_cache_step(PanWindow *pw)
 {
 	FileData *fd;
 	PanCacheData *pc;
-	CacheData *cd = NULL;
-
-	if (!pw->cache_todo) return FALSE;
+	CacheDataType load_mask;
+
+	if (!pw->cache_todo) return TRUE;
 
 	fd = pw->cache_todo->data;
 	pw->cache_todo = g_list_remove(pw->cache_todo, fd);
 
+#if 0
 	if (enable_thumb_caching)
 		{
 		gchar *found;
@@ -495,16 +527,68 @@
 
 		pw->cache_tick = 9;
 		}
-
+#endif
 	pc = g_new0(PanCacheData, 1);
 	memcpy(pc, fd, sizeof(FileData));
 	g_free(fd);
 
-	pc->cd = cd;
+	pc->cd = NULL;
 
 	pw->cache_list = g_list_prepend(pw->cache_list, pc);
 
-	return TRUE;
+	cache_loader_free(pw->cache_cl);
+
+	load_mask = CACHE_LOADER_NONE;
+	if (pw->size > LAYOUT_SIZE_THUMB_LARGE) load_mask |= CACHE_LOADER_DIMENSIONS;
+	if (SORT_BY_EXIF_DATE) load_mask |= CACHE_LOADER_DATE;
+	pw->cache_cl = cache_loader_new(((FileData *)pc)->path, load_mask,
+					pan_cache_step_done_cb, pw);
+	return (pw->cache_cl == NULL);
+}
+
+static void pan_cache_sync_date(PanWindow *pw, GList *list)
+{
+	GList *haystack;
+	GList *work;
+
+	haystack = g_list_copy(pw->cache_list);
+
+	work = list;
+	while (work)
+		{
+		FileData *fd;
+		GList *needle;
+
+		fd = work->data;
+		work = work->next;
+
+		needle = haystack;
+		while (needle)
+			{
+			PanCacheData *pc;
+			gchar *path;
+
+			pc = needle->data;
+			path = ((FileData *)pc)->path;
+			if (path && strcmp(path, fd->path) == 0)
+				{
+				GList *tmp;
+
+				if (pc->cd && pc->cd->have_date && pc->cd->date >= 0)
+					{
+					fd->date = pc->cd->date;
+					}
+
+				tmp = needle;
+				needle = needle->next;
+				haystack = g_list_delete_link(haystack, tmp);
+				}
+			else
+				{
+				needle = needle->next;
+				}
+			}
+		}
 }
 
 /*
@@ -1832,6 +1916,12 @@
 	list = pan_window_layout_list(path, SORT_NONE, TRUE);
 	list = filelist_sort(list, SORT_TIME, TRUE);
 
+	if (pw->cache_list && SORT_BY_EXIF_DATE)
+		{
+		pan_cache_sync_date(pw, list);
+		list = filelist_sort(list, SORT_TIME, TRUE);
+		}
+
 	day_max = 0;
 	count = 0;
 	tc = 0;
@@ -2047,6 +2137,12 @@
 	list = pan_window_layout_list(path, SORT_NONE, TRUE);
 	list = filelist_sort(list, SORT_TIME, TRUE);
 
+	if (pw->cache_list && SORT_BY_EXIF_DATE)
+		{
+		pan_cache_sync_date(pw, list);
+		list = filelist_sort(list, SORT_TIME, TRUE);
+		}
+
 	*width = PAN_FOLDER_BOX_BORDER * 2;
 	*height = PAN_FOLDER_BOX_BORDER * 2;
 
@@ -2092,6 +2188,7 @@
 				pi = pan_item_new_text(pw, x, y, buf,
 						       TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
 						       PAN_TEXT_COLOR, 255);
+				g_free(buf);
 				y += pi->height;
 
 				pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path),
@@ -3004,7 +3101,8 @@
 	gint scroll_x;
 	gint scroll_y;
 
-	if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
+	if (pw->size > LAYOUT_SIZE_THUMB_LARGE ||
+	    (SORT_BY_EXIF_DATE && (pw->layout == LAYOUT_TIMELINE || pw->layout == LAYOUT_CALENDAR)))
 		{
 		if (!pw->cache_list && !pw->cache_todo)
 			{
@@ -3015,7 +3113,7 @@
 				return TRUE;
 				}
 			}
-		if (pan_cache_step(pw))
+		if (pw->cache_todo)
 			{
 			pw->cache_count++;
 			pw->cache_tick++;
@@ -3035,7 +3133,10 @@
 				pw->cache_tick = 0;
 				}
 
-			return TRUE;
+			if (pan_cache_step(pw)) return TRUE;
+
+			pw->idle_id = -1;
+			return FALSE;
 			}
 		}
 
@@ -3070,7 +3171,6 @@
 	pan_window_message(pw, NULL);
 
 	pw->idle_id = -1;
-
 	return FALSE;
 }
 
@@ -3078,11 +3178,16 @@
 {
 	if (pw->idle_id == -1)
 		{
-		pan_window_message(pw, _("Sorting images..."));
 		pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw);
 		}
 }
 
+static void pan_window_layout_update(PanWindow *pw)
+{
+	pan_window_message(pw, _("Sorting images..."));
+	pan_window_layout_update_idle(pw);
+}
+
 /*
  *-----------------------------------------------------------------------------
  * pan window keyboard
@@ -3998,7 +4103,7 @@
 	PanWindow *pw = data;
 
 	pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
-	pan_window_layout_update_idle(pw);
+	pan_window_layout_update(pw);
 }
 
 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
@@ -4006,7 +4111,7 @@
 	PanWindow *pw = data;
 
 	pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
-	pan_window_layout_update_idle(pw);
+	pan_window_layout_update(pw);
 }
 
 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
@@ -4022,15 +4127,18 @@
 		warning_dialog(_("Folder not found"),
 			       _("The entered path is not a folder"),
 			       GTK_STOCK_DIALOG_WARNING, pw->path_entry);
-		return;
 		}
-
-	tab_completion_append_to_history(pw->path_entry, path);
-
-	g_free(pw->path);
-	pw->path = g_strdup(path);
-
-	pan_window_layout_update_idle(pw);
+	else
+		{
+		tab_completion_append_to_history(pw->path_entry, path);
+
+		g_free(pw->path);
+		pw->path = g_strdup(path);
+
+		pan_window_layout_update(pw);
+		}
+
+	g_free(path);
 }
 
 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
@@ -4058,6 +4166,7 @@
 	gtk_widget_destroy(pw->window);
 
 	pan_window_items_free(pw);
+	pan_cache_free(pw);
 
 	g_free(pw->path);
 
@@ -4263,7 +4372,7 @@
 
 	gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
 
-	pan_window_layout_update_idle(pw);
+	pan_window_layout_update(pw);
 
 	gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
 	gtk_widget_show(pw->window);
@@ -4554,7 +4663,7 @@
 			g_free(pw->path);
 			pw->path = g_strdup(path);
 
-			pan_window_layout_update_idle(pw);
+			pan_window_layout_update(pw);
 			}
 
 		path_list_free(list);