From 1e84773276ede56309a145dc08a4f3d47ad3a29f Mon Sep 17 00:00:00 2001
From: Bert <ber.t@gmx.com>
Date: Fri, 2 Sep 2011 04:33:44 +0200
Subject: [PATCH] Data driven timeout handling

---
 Makefile   |   2 +-
 commands.c |  25 ++++----
 main.c     | 180 +++++++++++++++++++++++++++++++++--------------------
 types.h    |   8 ++-
 util.c     |  13 ----
 util.h     |   9 ++-
 6 files changed, 137 insertions(+), 100 deletions(-)

diff --git a/Makefile b/Makefile
index ade1b3e..07c577a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 all: sxiv
 
-VERSION = git-20110819
+VERSION = git-20110902
 
 CC = gcc
 DESTDIR =
diff --git a/commands.c b/commands.c
index 51f7a55..123d4f5 100644
--- a/commands.c
+++ b/commands.c
@@ -30,6 +30,11 @@
 void cleanup();
 void remove_file(int, unsigned char);
 void load_image(int);
+void redraw();
+void hide_cursor();
+void animate();
+void set_timeout(timeout_f, int, int);
+void reset_timeout(timeout_f);
 
 extern appmode_t mode;
 extern img_t img;
@@ -39,10 +44,6 @@ extern win_t win;
 extern fileinfo_t *files;
 extern int filecnt, fileidx;
 
-extern int timo_cursor;
-extern int timo_redraw;
-extern int timo_adelay;
-
 int it_quit(arg_t a) {
 	cleanup();
 	exit(0);
@@ -54,12 +55,11 @@ int it_switch_mode(arg_t a) {
 			tns_init(&tns, filecnt);
 		img_close(&img, 0);
 		win_set_cursor(&win, CURSOR_ARROW);
-		timo_cursor = 0;
+		reset_timeout(hide_cursor);
 		tns.sel = fileidx;
 		tns.dirty = 1;
 		mode = MODE_THUMB;
 	} else {
-		timo_cursor = TO_CURSOR_HIDE;
 		load_image(tns.sel);
 		mode = MODE_IMAGE;
 	}
@@ -68,11 +68,11 @@ int it_switch_mode(arg_t a) {
 
 int it_toggle_fullscreen(arg_t a) {
 	win_toggle_fullscreen(&win);
+	set_timeout(redraw, TO_REDRAW_RESIZE, 0);
 	if (mode == MODE_IMAGE)
 		img.checkpan = 1;
 	else
 		tns.dirty = 1;
-	timo_redraw = TO_WIN_RESIZE;
 	return 0;
 }
 
@@ -156,15 +156,18 @@ int i_navigate_frame(arg_t a) {
 }
 
 int i_toggle_animation(arg_t a) {
+	int delay;
+
 	if (mode != MODE_IMAGE)
 		return 0;
 
 	if (img.multi.animate) {
-		timo_adelay = 0;
+		reset_timeout(animate);
 		img.multi.animate = 0;
 		return 0;
 	} else {
-		timo_adelay = img_frame_animate(&img, 1);
+		delay = img_frame_animate(&img, 1);
+		set_timeout(animate, delay, 1);
 		return 1;
 	}
 }
@@ -245,8 +248,8 @@ int i_drag(arg_t a) {
 	}
 	
 	win_set_cursor(&win, CURSOR_ARROW);
-	timo_cursor = TO_CURSOR_HIDE;
-	timo_redraw = 0;
+	set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
+	reset_timeout(redraw);
 
 	return 0;
 }
diff --git a/main.c b/main.c
index 6545cfb..5866823 100644
--- a/main.c
+++ b/main.c
@@ -41,6 +41,17 @@ enum {
 	FNAME_CNT = 1024
 };
 
+typedef struct {
+	struct timeval when;
+	Bool active;
+	timeout_f handler;
+} timeout_t;
+
+/* timeout handler functions: */
+void redraw();
+void hide_cursor();
+void animate();
+
 appmode_t mode;
 img_t img;
 tns_t tns;
@@ -52,9 +63,11 @@ size_t filesize;
 
 char win_title[TITLE_LEN];
 
-int timo_cursor;
-int timo_redraw;
-int timo_adelay; /* multi-frame animation delay time */
+timeout_t timeouts[] = {
+	{ { 0, 0 }, False, redraw },
+	{ { 0, 0 }, False, hide_cursor },
+	{ { 0, 0 }, False, animate }
+};
 
 void cleanup() {
 	static int in = 0;
@@ -120,6 +133,54 @@ void remove_file(int n, unsigned char silent) {
 		tns.cnt--;
 }
 
+void set_timeout(timeout_f handler, int time, int overwrite) {
+	int i;
+
+	for (i = 0; i < LEN(timeouts); i++) {
+		if (timeouts[i].handler == handler) {
+			if (!timeouts[i].active || overwrite) {
+				gettimeofday(&timeouts[i].when, 0);
+				MSEC_ADD_TO_TIMEVAL(time, &timeouts[i].when);
+				timeouts[i].active = True;
+			}
+			return;
+		}
+	}
+}
+
+void reset_timeout(timeout_f handler) {
+	int i;
+
+	for (i = 0; i < LEN(timeouts); i++) {
+		if (timeouts[i].handler == handler) {
+			timeouts[i].active = False;
+			return;
+		}
+	}
+}
+
+int check_timeouts(struct timeval *t) {
+	int i, tdiff, tmin = -1;
+	struct timeval now;
+
+	gettimeofday(&now, 0);
+	for (i = 0; i < LEN(timeouts); i++) {
+		if (timeouts[i].active) {
+			tdiff = TIMEDIFF(&timeouts[i].when, &now);
+			if (tdiff <= 0) {
+				timeouts[i].active = False;
+				if (timeouts[i].handler)
+					timeouts[i].handler();
+			} else if (tmin < 0 || tdiff < tmin) {
+				tmin = tdiff;
+			}
+		}
+	}
+	if (tmin > 0 && t)
+		MSEC_TO_TIMEVAL(tmin, t);
+	return tmin > 0;
+}
+
 void load_image(int new) {
 	struct stat fstats;
 
@@ -144,9 +205,9 @@ void load_image(int new) {
 
 	if (img.multi.cnt) {
 		if (img.multi.animate)
-			timo_adelay = img.multi.frames[img.multi.sel].delay;
+			set_timeout(animate, img.multi.frames[img.multi.sel].delay, 1);
 		else
-			timo_adelay = 0;
+			reset_timeout(animate);
 	}
 }
 
@@ -186,15 +247,31 @@ void update_title() {
 void redraw() {
 	if (mode == MODE_IMAGE) {
 		img_render(&img, &win);
-		if (timo_cursor)
-			win_set_cursor(&win, CURSOR_ARROW);
-		else
+		if (img.multi.animate) {
 			win_set_cursor(&win, CURSOR_NONE);
+		} else {
+			win_set_cursor(&win, CURSOR_ARROW);
+			set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
+		}
 	} else {
 		tns_render(&tns, &win);
 	}
 	update_title();
-	timo_redraw = 0;
+	reset_timeout(redraw);
+}
+
+void hide_cursor() {
+	win_set_cursor(&win, CURSOR_NONE);
+}
+
+void animate() {
+	int delay;
+
+	delay = img_frame_animate(&img, 0);
+	if (delay) {
+		set_timeout(animate, delay, 1);
+		redraw();
+	}
 }
 
 Bool keymask(const keymap_t *k, unsigned int state) {
@@ -233,7 +310,7 @@ void on_buttonpress(XButtonEvent *bev) {
 
 	if (mode == MODE_IMAGE) {
 		win_set_cursor(&win, CURSOR_ARROW);
-		timo_cursor = TO_CURSOR_HIDE;
+		set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
 
 		for (i = 0; i < LEN(buttons); i++) {
 			if (buttons[i].button == bev->button &&
@@ -252,7 +329,7 @@ void on_buttonpress(XButtonEvent *bev) {
 					if (sel == tns.sel) {
 						load_image(tns.sel);
 						mode = MODE_IMAGE;
-						timo_cursor = TO_CURSOR_HIDE;
+						set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
 					} else {
 						tns_highlight(&tns, &win, tns.sel, False);
 						tns_highlight(&tns, &win, sel, True);
@@ -272,75 +349,39 @@ void on_buttonpress(XButtonEvent *bev) {
 }
 
 void run() {
-	int xfd, timeout;
+	int xfd;
 	fd_set fds;
-	struct timeval tt, t0, t1;
+	struct timeval timeout;
 	XEvent ev;
 
-	timo_cursor = mode == MODE_IMAGE ? TO_CURSOR_HIDE : 0;
-
 	redraw();
 
 	while (1) {
-		if (mode == MODE_THUMB && tns.cnt < filecnt) {
+		if (!XPending(win.env.dpy)) {
 			/* load thumbnails */
-			win_set_cursor(&win, CURSOR_WATCH);
-			gettimeofday(&t0, 0);
-
-			while (tns.cnt < filecnt && !XPending(win.env.dpy)) {
+			while (mode == MODE_THUMB && tns.cnt < filecnt) {
+				win_set_cursor(&win, CURSOR_WATCH);
 				if (tns_load(&tns, tns.cnt, &files[tns.cnt], False, False))
 					tns.cnt++;
 				else
 					remove_file(tns.cnt, 0);
-				gettimeofday(&t1, 0);
-				if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD)
-					break;
-			}
-			if (tns.cnt == filecnt)
-				win_set_cursor(&win, CURSOR_ARROW);
-			if (!XPending(win.env.dpy)) {
-				redraw();
-				continue;
-			} else {
-				timo_redraw = TO_THUMBS_LOAD;
-			}
-		}
-		
-		if (timo_cursor || timo_redraw || timo_adelay) {
-			/* check active timeouts */
-			gettimeofday(&t0, 0);
-			timeout = min_int_nz(3, timo_cursor, timo_redraw, timo_adelay);
-			MSEC_TO_TIMEVAL(timeout, &tt);
-			xfd = ConnectionNumber(win.env.dpy);
-			FD_ZERO(&fds);
-			FD_SET(xfd, &fds);
-
-			if (!XPending(win.env.dpy))
-				select(xfd + 1, &fds, 0, 0, &tt);
-			gettimeofday(&t1, 0);
-			timeout = MIN(TIMEDIFF(&t1, &t0), timeout);
-
-			/* timeouts fired? */
-			if (timo_cursor) {
-				timo_cursor = MAX(0, timo_cursor - timeout);
-				if (!timo_cursor)
-					win_set_cursor(&win, CURSOR_NONE);
-			}
-			if (timo_redraw) {
-				timo_redraw = MAX(0, timo_redraw - timeout);
-				if (!timo_redraw)
+				if (tns.cnt == filecnt) {
 					redraw();
-			}
-			if (timo_adelay) {
-				timo_adelay = MAX(0, timo_adelay - timeout);
-				if (!timo_adelay) {
-					if ((timo_adelay = img_frame_animate(&img, 0)))
-						redraw();
+					win_set_cursor(&win, CURSOR_ARROW);
+				} else {
+					set_timeout(redraw, TO_REDRAW_THUMBS, 0);
+					check_timeouts(NULL);
 				}
 			}
-			if ((timo_cursor || timo_redraw || timo_adelay) &&
-			    !XPending(win.env.dpy))
-				continue;
+
+			/* handle timeouts */
+			if (check_timeouts(&timeout)) {
+				xfd = ConnectionNumber(win.env.dpy);
+				FD_ZERO(&fds);
+				FD_SET(xfd, &fds);
+				if (!select(xfd + 1, &fds, 0, 0, &timeout))
+					check_timeouts(NULL);
+			}
 		}
 
 		if (!XNextEvent(win.env.dpy, &ev)) {
@@ -355,7 +396,7 @@ void run() {
 					break;
 				case ConfigureNotify:
 					if (win_configure(&win, &ev.xconfigure)) {
-						timo_redraw = TO_WIN_RESIZE;
+						set_timeout(redraw, TO_REDRAW_RESIZE, 0);
 						if (mode == MODE_IMAGE)
 							img.checkpan = 1;
 						else
@@ -366,9 +407,10 @@ void run() {
 					on_keypress(&ev.xkey);
 					break;
 				case MotionNotify:
-					if (!timo_cursor)
+					if (mode == MODE_IMAGE) {
 						win_set_cursor(&win, CURSOR_ARROW);
-					timo_cursor = TO_CURSOR_HIDE;
+						set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
+					}
 					break;
 			}
 		}
diff --git a/types.h b/types.h
index f19d693..bf8d4e2 100644
--- a/types.h
+++ b/types.h
@@ -33,9 +33,11 @@ typedef struct {
 
 /* timeouts in milliseconds: */
 enum {
-	TO_WIN_RESIZE  = 75,
-	TO_CURSOR_HIDE = 1500,
-	TO_THUMBS_LOAD = 200
+	TO_REDRAW_RESIZE = 75,
+	TO_REDRAW_THUMBS = 200,
+	TO_CURSOR_HIDE   = 1500
 };
 
+typedef void (*timeout_f)(void);
+
 #endif /* TYPES_H */
diff --git a/util.c b/util.c
index 5bed511..0c4574e 100644
--- a/util.c
+++ b/util.c
@@ -87,19 +87,6 @@ void die(const char* fmt, ...) {
 	exit(1);
 }
 
-int min_int_nz(int n, ...) {
-	va_list args;
-	int i, a, m = 0;
-
-	va_start(args, n);
-	for (i = 0; i < n; i++) {
-		if ((a = va_arg(args, int)) && (!m || a < m))
-			m = a;
-	}
-	va_end(args);
-	return m;
-}
-
 void size_readable(float *size, const char **unit) {
 	const char *units[] = { "", "K", "M", "G" };
 	int i;
diff --git a/util.h b/util.h
index a891054..19ebcd7 100644
--- a/util.h
+++ b/util.h
@@ -33,7 +33,12 @@
 
 #define MSEC_TO_TIMEVAL(t,tv) {         \
   (tv)->tv_sec = (t) / 1000;            \
-	(tv)->tv_usec = (t) % 1000 * 1000;    \
+  (tv)->tv_usec = (t) % 1000 * 1000;    \
+}
+
+#define MSEC_ADD_TO_TIMEVAL(t,tv) {     \
+  (tv)->tv_sec += (t) / 1000;           \
+  (tv)->tv_usec += (t) % 1000 * 1000;   \
 }
 
 #ifndef TIMESPEC_TO_TIMEVAL
@@ -60,8 +65,6 @@ char* s_strdup(char*);
 void warn(const char*, ...);
 void die(const char*, ...);
 
-int min_int_nz(int, ...);
-
 void size_readable(float*, const char**);
 
 char* absolute_path(const char*);