/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis */
#define GUASH_TIMESTAMP "Time-stamp: <2001/02/08 23:11:30 narazaki@gimp.org>"
/* guash.c -- This is a plug-in/extension for the GIMP 1.X
 * Guash: Gimp Users' Another SHell (pronounced like 'gouache')
 * Copyright (C) 1997-2001 Shuji Narazaki <narazaki@gimp.org>
 * guash uses internally for loading xv's thumbnail:
 * xvpict (Copyright (C) 1997 Marco Lamberto <lm@geocities.com>)
 * (slighly modified xvpict is embedded into guash for reducing communication
 * overhead)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * THANKS
 * this release of guash was applied patches from:
 *  Marco Lamberto <lm@geocities.com>
 *  Vincent Hascoet <hascoet@lep.research.philips.com>
 *  J"urgen Koslowski <koslowj@iti.cs.tu-bs.de>
 *  meo@netads.com (Miles O'Neal)
 *  Casper Maarbjerg <casper@tv1.dk>
 *  Drew Ronneberg <drew@rnaworld.princeton.edu>
 *  craig@pablo.ppco.com (Craig Brockmeier)
 *  Colin Plumb <colin@nyx.net>
 *  Shawn Houston <houston@arsc.edu>
 *  Masahiro Sakai <zvm01052@nifty.ne.jp>
 */

#include <gtk/gtk.h>
#include <gtk/gtkfeatures.h>
#include <gdk/gdkkeysyms.h>
#ifndef GDK_WINDOWING_WIN32
#include <gdk/gdkx.h>
#endif
#include <gdk/gdkprivate.h>
#include <libgimp/gimp.h>
#ifdef GUASH_I18N
#include <libgimp/stdplugins-intl.h>
#else
#ifdef GIMP_VERSION
#define N_(x)	x
#define _(x)	x
#endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef NATIVE_WIN32
#include <dirent.h>
#endif
#include <fcntl.h>
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#include <errno.h>
#include "guash-config.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef NATIVE_WIN32
#include <process.h>
#endif

#include "VERSION"
#include "include/debug.h"
#ifdef GIMP_VERSION
#include "data/guash-banner.h"
#include "data/guash-dir_icon.h"
#include "data/guash-pdir_icon.h"
#include "data/guash-file_icon.h"
#else
#include "lib/image_convert.h"
#include "lib/image_pdb.h"
#include "data/icons.h"
#ifdef WIN32
typedef gint mode_t;
#endif
#endif

#ifdef NATIVE_WIN32
#define sleep(s) g_usleep (s*1000000)
#define readlink(p, b, l) (-1)
#endif

/* The following definition is only for narazaki@gimp.org (linux user) */
#ifdef CHECK_PERFORMANCE
# include <sys/times.h>
# define WATCH(exp)	\
{ \
  struct tms	_t;\
  gulong	_start = (gulong) times (&_t);\
  exp;\
  printf ("Elapsed time: %ld msec.\n", (gulong) times (&_t) - _start);\
}
#else
# define WATCH(exp)	exp
#endif

/* CONSTANTS */
#ifdef DEBUG
# define PLUG_IN_NAME		"extension_guash_debug"
# define PLUG_IN_NAME_SUB	"plug_in_guash_debug"
# define PLUG_IN_NAME_FOR_SELECTION "extension_guash_script_fu_interface"
# define SHORT_NAME		"Guash (debug)"
# define MENU_PATH_AS_EXTENSION	"<Toolbox>/Xtns/Guash (debug)"
# define MENU_PATH_AS_PLUG_IN	"<Image>/File/Guash (debug)"
#else
# define PLUG_IN_NAME		"extension_guash"
# define PLUG_IN_NAME_SUB	"plug_in_guash"
# define PLUG_IN_NAME_FOR_SELECTION "extension_guash_script_fu_interface"
# define SHORT_NAME		"Guash"
# define MENU_PATH_AS_EXTENSION	"<Toolbox>/Xtns/Guash"
# define MENU_PATH_AS_PLUG_IN	"<Image>/File/Guash"
#endif

#define GUASH_DND_SIGNATURE	"file:"
#define THUMBNAIL_DIR		"/.xvpics"
#define DELETE_CORE		TRUE

#ifdef GIMP_HAVE_PARASITES
#define GUASH_PARASITE_NAME	"guash-selection"
#endif

/* VISUAL PARAMETERS */
#define JUMP_BUTTON_WIDTH	40 	/* change it if you use a large font */
#define LABEL_PADDING		4 	/* [pixel] from left margin */
#define	SCROLLBAR_WIDTH		15	/* the width of scrollbar */
#define THUMBNAIL_TSEPARATOR	2	/* v. pad between thumb. and name */
#define THUMBNAIL_BORDER_WIDTH	2 	/* width of selection border */
#define CURSOR_DEFAULT		GDK_TOP_LEFT_ARROW
#define CURSOR_HAND		GDK_HAND2
#define CURSOR_WAIT		GDK_WATCH
#define NCOL_OF_THUMBNAIL_PANEL_MIN	4 /* minimal size (width) of panel */
#define NROW_OF_THUMBNAIL_PANEL_MIN	1 /* minimal size (height) of panel */

/* VISUAL CONSTANTS */
#define THUMBNAIL_WIDTH		80	/* Max width of XV thumbnail */
#define THUMBNAIL_HEIGHT	60	/* Max height of XV thumbnail */

#define LABEL_WIDTH(x)		((x) - JUMP_BUTTON_WIDTH - LABEL_PADDING)
#define THUMBNAIL_THEIGHT	(the_panel.font_height + THUMBNAIL_TSEPARATOR)
#define THUMBNAIL_FULLHEIGHT	(THUMBNAIL_HEIGHT + THUMBNAIL_THEIGHT)
#define THUMBNAIL_FULLHEIGHT	(THUMBNAIL_HEIGHT + THUMBNAIL_THEIGHT)
#define THUMBNAIL_SEPARATOR	(2 * THUMBNAIL_BORDER_WIDTH + 3)

/* OTHER CONSTANTS */
#define INITIALIZATION_DELAY	100		/* [msec] */
#define BANNER_UP_PERIOD	200		/* [msec] */
#define INITIALIZATION_SLEEP_PERIOD	1200	/* [msec] */
#define STARTUP_SLEEP_PERIOD		300	/* [msec] */
#define EVENT_MASK         (GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | \
                           GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | \
			   GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK | \
			   GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | \
			   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | \
			   GDK_PROXIMITY_OUT_MASK | GDK_FOCUS_CHANGE_MASK)
#define	LINE_BUF_SIZE	1024
#define PATH_LENGTH	2049
#define COMMENT_DELIMITOR	','

#define INTERFACE		guash_interface
#define	DIALOG			guash_dialog
#define VALS			guash_vals

#define INDEX_TO_X(i)	(((i) % the_panel.ncol)\
			 * (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)\
			 + THUMBNAIL_SEPARATOR)
#define INDEX_TO_Y(i)	((((i) % the_panel.nthumb_in_page) / the_panel.ncol)\
			 * (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
			 + THUMBNAIL_SEPARATOR)
#define VALID_POS_P(x,y)	(((y) < \
				  (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
				  * the_panel.nrow)\
				 && \
				 (((x) < \
				   (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)\
				   * the_panel.ncol)))
#define POS_TO_INDEX(x,y)	(cwd_cache->display_page * the_panel.nthumb_in_page \
				 + ((y) - THUMBNAIL_SEPARATOR) \
				 / (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
				 * the_panel.ncol \
				 + (x) / (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR))
#define COL2WIDTH(col)	((col) * (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR) + THUMBNAIL_SEPARATOR)
#define ROW2HEIGHT(row)	((row) * (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR) + THUMBNAIL_SEPARATOR)
#define WIDTH2COL(w)	((w) - THUMBNAIL_SEPARATOR) / (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)
#define HEIGHT2ROW(h)	((h) - THUMBNAIL_SEPARATOR) / (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)

/* programming stuff */
#define ASSERT(predicate)	if (! (predicate)) return

#define BUILD_ABSOLUTE_PATH(buf, path, fname)	\
{ if (! strcmp (path, G_DIR_SEPARATOR_S)) \
    sprintf (buf, "%c%s", G_DIR_SEPARATOR, fname); \
  else \
    sprintf (buf, "%s%c%s", path, G_DIR_SEPARATOR, fname); \
}

#define BUILD_ABSOLUTE(buf, fname)	BUILD_ABSOLUTE_PATH(buf, cwd_cache->name, fname);
#define DURING_DRAG_MOTION_P	(0 <= dnd_record.index)

/* gtkWrapper */
/* gtkW is the abbreviation of gtk Wrapper */
#define GTKW_ENTRY_BUFFER_SIZE	3
#define GTKW_ENTRY_WIDTH	20
#define GTKW_SCALE_WIDTH	200
#define	GTKW_BORDER_WIDTH	0

/* gtkW type */
typedef struct
{
  GtkWidget	*widget;
  gpointer	value;
  void	 	(*updater)();
} gtkW_widget_table;
gtkW_widget_table	widget_pointer[1];

/* gtkW global variables */
gint	gtkW_border_width = GTKW_BORDER_WIDTH;
gint	gtkW_border_height = 0;
gint	gtkW_homogeneous_layout	= FALSE;
gint	gtkW_frame_shadow_type = GTK_SHADOW_ETCHED_IN;
gint	gtkW_align_x = GTK_FILL;
gint	gtkW_align_y = GTK_FILL;

#include "include/gtkW.h"

/* define enums */
/* for binding_style {BINDING_GIMP, BINDING_EMACS } I*/
#define BINDING_GIMP	0
#define BINDING_EMACS	1
#define NBINDINGS	2	/* number of bindings */

/* for file_kind { NOT_EXIST, REGFILE, DIRECTORY, SLNKFILE, SLNKDIR } */
#define NOT_EXIST	0
#define REGFILE		(1 << 0)
#define DIRECTORY	(1 << 1)
#define SLNKFILE	(1 << 2)
#define SLNKDIR		(1 << 3)

/* for directory_cache.savable { FALSE, TRUE, UNKNOWN } */
#define UNKNOWN		2

/* for selection_kind { FALSE, SELECTION_IMAGE, SELECTION_DIRECTORY } */
#define	SELECTION_IMAGE	1
#define SELECTION_DISPLAY 2

/* for sensives { SENSITIVE_SELECTION, SENSITIVE_NOT_LAST_PAGE, SENSITIVE_NOT_FIRST_PAGE } */
#define SENSITIVE_SELECTION	(1 << 0)
#define SENSITIVE_NOT_LAST_PAGE (1 << 1)
#define SENSITIVE_NOT_FIRST_PAGE (1 << 2)

/* getters and setters for misc attributes for structs */
#define INITIALIZE_ATTRIBUTES(obj)	((obj)->attributes = 0)
/* assumed each attribute is represented by 1 bit. bogus. */
#define GET_ATTRIBUTE(obj, key)		(((obj)->attributes & (key)) >> (key))
#define HAS_ATTRIBUTE(obj, key)		((obj)->attributes & (key))
#define HAS_NO_ATTRIBUTE(obj, key)	(! ((obj)->attributes & (key)))
#define SET_ATTRIBUTE(obj, key)		((obj)->attributes |= (key))
#define RESET_ATTRIBUTE(obj, key)	((obj)->attributes &= ~(key))
#define SET_TO_ATTRIBUTE(obj, key, val)	((val) ? SET_ATTRIBUTE(obj, key) : RESET_ATTRIBUTE(obj, key))
#define COPY_ATTRIBUTE(dest, key, src)	(SET_TO_ATTRIBUTE(dest, key, HAS_ATTRIBUTE(src, key)))
#define TOGGLE_ATTRIBUTE(obj, key)	(SET_TO_ATTRIBUTE(obj, key, HAS_NO_ATTRIBUTE(obj, key)))

/* attributes for VAL */
#define DISPLAY_IMAGE_P		(1 << 0)
#define SORT_BY_NAME_P		(1 << 1)
/* SAVE_AS_XVPICT_P should be used only for save thumbnail.
   And don't use to determine whether thumbnails were saved */
#define SAVE_AS_XVPICT_P	(1 << 2)
#define SAVE_AS_GPICT_P		(1 << 3)
#define CONFIRM_P		(1 << 4)
#define MONITOR_P		(1 << 5)
#define PURGED_P		(1 << 6)
#define DRAG_MOVE_P		(1 << 7)

typedef struct
{
  guint	attributes;
  GCHAR last_dir_name[PATH_LENGTH];
  GCHAR mapping_command[LINE_BUF_SIZE];
} VALS;

typedef struct
{
  guchar	*data;
  gint		width;
  gint		height;
  gint		ch;
  gint		size;
} image_buffer;

/* attributes for Thumbnail */
#define DIRECTORY_P		(1 << 0)
#define SYMLINK_P		(1 << 1)
#define DELETED_P		(1 << 2)
#define PARENT_DIRECTORY_P	(1 << 3)
#define SELECTED_P		(1 << 4)

typedef struct
{
  guint		attributes;		/* 5 collective attributes above */
  image_buffer	*image;
  GCHAR		*name;
  GCHAR		*info;
  gint		selection_timestamp;
} Thumbnail;

typedef struct
{
  GdkPixmap	*pixmap;
  guchar	*line_buffer;
  gint		width;
  gint		height;
  GdkGC		*gc;
  GdkColor	black;
  GdkColor	white;
} Pixmap_struct;

/* attributes for directory_cache */
/* #define DISPLAY_IMAGE_P	shared with VALS */
/* #define SORT_BY_NAME_P	shared with VALS */
#define FILED_P			(1 << 2)
#define DISORDERED_P		(1 << 3)

typedef struct
{
  GCHAR		name[PATH_LENGTH];
  guint		attributes;	/* filed and purged */
  gint		birth_index;
  gshort	savable;	/* three values */
  Thumbnail	*image;
  Thumbnail	*dir;
  gint		ndir;
  gint		nimage;
  gint		max_ndir;
  gint		max_nimage;
  gint		display_page;
  gint		selection_timestamp;
  gint		num_selection;
  gint		selection_top;
  gint		last_focus;
  gint		last_focus_directory;
  gdouble	timestamp;
} directory_cache;

typedef struct
{
  guint			key;
  GdkModifierType	mod;
} Gtk_binding;

typedef struct
{
  GCHAR		*label;
  void		(* command) (GtkWidget *, gpointer);
  gint		sensitive;
  GtkWidget	*widget;
  Gtk_binding	binding[NBINDINGS];
  GtkWidget	**submenu;
} image_command_table;

typedef struct
{
  GCHAR	*action;
  GCHAR	*condition;
  GCHAR	*behavior;
  gint	flush_left;
} help_table;

struct
{
  gint		width;
  gint		height;
  gint		ncol;
  gint		nrow;
  gint		nthumb_in_page;
  gint		resized;
  gint		current_page1;
  gint		current_page;
  gint		last_index;
  gint		font_height;
  GCHAR		info[256];
  GdkGC		*gc;
  image_buffer  *bg;
  gboolean	in_motion_p;
  GdkFont	*font;
  /* for rubber band handling */
  gint		start_x;
  gint		start_y;
  gint		end_x;
  gint		end_y;
} the_panel;

#define	RESET_RUBBER_AREA	\
	{  \
	   the_panel.start_x = the_panel.start_y = -1; \
	   the_panel.end_x = the_panel.end_y = -1; \
	}
#define	RUBBER_BAND_P	( the_panel.start_y != -1 )

typedef struct
{
  gint		index;
  gint		selection_timestamp;
  Thumbnail	*thumbnail;
} selection_iterator_entry;

typedef struct
{
  gint				length;
  gint				unvisit_index;
  selection_iterator_entry	*body;
} selection_iterator;

typedef struct
{
  GString	*drag_data;
  directory_cache	*drag_start;
  guint		time;
  gint		index;
  gboolean	internal_action;
  guint32	timeout_handler;
} DndRecord;

typedef struct
{
  gboolean	clear;
  gboolean	repaint;
  gboolean	repaint_rect;
  gint		x;
  gint		y;
  gint		width;
  gint		height;
  gboolean	finalize;
} DisplayRequest;

VALS		VAL;
directory_cache *cwd_cache = NULL;
Thumbnail	*the_loaded_data = NULL;
image_buffer	*banner;
image_buffer	*dir_icon;
image_buffer	*pdir_icon;
image_buffer	*file_icon;
Pixmap_struct	*text_buffer = NULL;
GHashTable	*directory_cache_table;
gint		directory_cache_max_images = 1024;
gint		directory_cache_table_max_size = DIRECTORY_CACHE_TABLE_MAX_SIZE;
gint		directory_cache_table_size = 0;
GtkWidget	*dlg = NULL;
GtkWidget	*cwd_label = NULL;
GtkWidget	*thumbnail_panel = NULL;
GtkWidget	*file_property = NULL;
GtkWidget	*thumbnail_panel_root_menu = NULL;
GtkWidget	*thumbnail_panel_preference_menu = NULL;
GtkWidget	*thumbnail_panel_directory_menu = NULL;
GtkWidget	*thumbnail_panel_selection_menu = NULL;
GtkWidget	*thumbnail_panel_selection_map_menu = NULL;
GtkWidget	*thumbnail_panel_hidden_menu = NULL;
GCHAR		**inhibit_suffix_table = NULL;
gint		num_inhibit_suffix = -1;
gint		selection_kind = FALSE;
guchar		thumbnail_colormap[256 * 3];
GCHAR		fileselector_last_pathname[PATH_LENGTH];
gint		guash_is_initialized = FALSE;
GCHAR		*guash_tmp_dir = NULL;
gint		binding_style = BINDING_GIMP;
gint		directory_monitor_interval = 0;
gint		delayed_updating = 0; /* This is not a flag: a kind of countable semaphore */
gboolean	during_buildup_thumbnail_panel = FALSE;
gboolean	emergency_termination = FALSE;
gint		with_lock = 0;
DndRecord	dnd_record = { NULL, NULL, 0, -1, FALSE, 0 };
DisplayRequest	display_request;
guint		display_request_handler_id = 0;

#define NSCROLLER	2
GtkWidget	*widget_for_scroll[NSCROLLER];

enum {
  TARGET_STRING,
  TARGET_URI_LIST,
  TARGET_TEXT_PLAIN,
} TargetType;

static GtkTargetEntry dnd_target_table[] =
{
  { "STRING", 0, TARGET_STRING },
  { "text/uri-list", 0, TARGET_URI_LIST },
  { "text/plain", 0, TARGET_STRING }
};
static guint dnd_n_targets = sizeof(dnd_target_table) / sizeof(dnd_target_table[0]);

#define	NSELECTION_BUTTON	2
GtkWidget	*widget_for_selecion[NSELECTION_BUTTON];

#define HEADER_PIXEL(data,pixel) {\
  pixel[0] = (((data[0] - 33) << 2) | ((data[1] - 33) >> 4)); \
  pixel[1] = ((((data[1] - 33) & 0xF) << 4) | ((data[2] - 33) >> 2)); \
  pixel[2] = ((((data[2] - 33) & 0x3) << 6) | ((data[3] - 33))); \
  data += 4; }

#include "include/guash-f.h"

#define MENU_SEPARATOR   { NULL, NULL, 0, NULL, \
  			   { { 0, 0 }, \
			     { 0, 0 } \
			   }, \
			  NULL }
#define UNBOUND		{ 0, 0 }

image_command_table
preference_commandns [] =
{
  { "Change display mode", toggle_display_mode_callback, FALSE, NULL,
    { { 'S', GDK_SHIFT_MASK },
      { 'S', GDK_SHIFT_MASK }
    },
    NULL },
  { "Change thumbnail save mode", toggle_save_mode_callback, FALSE, NULL,
    { { 'T', 0 },
      { 'T', 0 }
    },
    NULL },
  { "Change sort mode", toggle_sort_mode_callback, FALSE, NULL,
    { { 'S', 0 },
      { 'S', 0 }
    },
    NULL }
};

image_command_table
image_directory_commands [] =
{
  { "Rename the directory to...", fileselector_for_move_directory_callback, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
  { "Try to mount", mount_callback, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
  { "Try to unmount", unmount_callback, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
  MENU_SEPARATOR,
  { "Remove the directory (rm -fr)", delete_directory_callback, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
};

image_command_table
image_root_commands [] =
{
  { "Next page", next_page_callback, SENSITIVE_NOT_LAST_PAGE, NULL,
    { { ' ', 0 },
      { ' ', 0 }
    },
    NULL },
  { "Prev page", prev_page_callback, SENSITIVE_NOT_FIRST_PAGE, NULL,
    { { 'B', 0 },
      { 'B', 0 }
    },
    NULL },
  { "First page", first_page_callback, SENSITIVE_NOT_FIRST_PAGE, NULL,
    { { GDK_Home, 0 },
      { '<', GDK_SHIFT_MASK }
    },
    NULL },
  { "Last page", last_page_callback, SENSITIVE_NOT_LAST_PAGE, NULL,
    { { GDK_End, 0 },
      { '>', GDK_SHIFT_MASK }
    },
    NULL },
  { "Selection All", select_all_callback, FALSE, NULL,
    { { 'A', GDK_CONTROL_MASK },
      { 'A', GDK_CONTROL_MASK }
    },
    NULL },
  { "Redraw", redraw_callback, FALSE, NULL,
    { UNBOUND,
      { 'L', GDK_CONTROL_MASK }
    },
    NULL },
  { "Update", update_callback, FALSE, NULL,
    { { 'R', 0 },
      { 'G', 0 }
    },
    NULL },
  MENU_SEPARATOR,
  { "Directory ops.", NULL, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    &thumbnail_panel_directory_menu },
#ifdef VISIBLE_HTML_THUMBNAIL_NAME
  MENU_SEPARATOR,
  { "Selection All jpeg thumbnail", select_all_jpeg_thumbnail_callback, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
#endif
  MENU_SEPARATOR,
  { "Make new directory", fileselector_for_mkdir_callback, FALSE, NULL,
    { { 'D', GDK_SHIFT_MASK },
      { '+', GDK_SHIFT_MASK }
    },
    NULL },
  { "Change directory", fileselector_for_chdir_callback, FALSE, NULL,
    { { 'C', 0 },
      { 'D', GDK_CONTROL_MASK }
    },
    NULL },
  { "Change to parent directory", parent_directory_callback, FALSE, NULL,
    { { '0', 0 },
      { '0', 0 }
    },
    NULL },
  { "Preferences...", NULL, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    &thumbnail_panel_preference_menu },
  { "Remove all thumbnail files", purge_thumbnail_file_callback, FALSE, NULL,
    { { 'R', GDK_MOD1_MASK },
      { 'P', GDK_MOD1_MASK }
    },
    NULL},
  MENU_SEPARATOR,
  { "Help", help_callback, FALSE, NULL,
    { { '?', 0 },
      { 'H', GDK_CONTROL_MASK }
    },
    NULL },
  { "Quit", gtkW_close_callback, FALSE, NULL,
    { { 'Q', 0 },
      { 'Q', GDK_CONTROL_MASK }
    },
    NULL }
};

image_command_table
image_selection_map_commands [] =
{
  { "Map script-fu on...", selection_map_script_callback, SENSITIVE_SELECTION, NULL,
    { { 'X', 0 },
      { '!', GDK_SHIFT_MASK }
    },
    NULL },
  { "Map unix command on...", selection_map_unix_command_callback, SENSITIVE_SELECTION, NULL,
    { { 'X', GDK_SHIFT_MASK },
      { '|', GDK_SHIFT_MASK }
    },
    NULL },
  MENU_SEPARATOR,
  { "Make index.html (plain list)", selection_dump_as_html_callback, SENSITIVE_SELECTION, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
  { "Make index.html (listing w/ side-caption)", selection_dump_as_html_listing_callback, SENSITIVE_SELECTION, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
  { "Make index.html (guash-style table)", selection_dump_as_html_guash_table_callback, SENSITIVE_SELECTION, NULL,
    { UNBOUND,
      UNBOUND
    },
    NULL },
};

image_command_table
image_selection_commands [] =
{
  { "Open", open_callback, SENSITIVE_SELECTION, NULL,
    { { 'O', GDK_CONTROL_MASK },
      { 'O', GDK_CONTROL_MASK }
    },
    NULL },
  { "Selection None", select_none_callback, FALSE, NULL,
    { { 'A', GDK_CONTROL_MASK|GDK_SHIFT_MASK },
      { 'A', GDK_CONTROL_MASK|GDK_SHIFT_MASK }
    },
    NULL },
  MENU_SEPARATOR,
  { "Copy to...", fileselector_for_copy_callback, SENSITIVE_SELECTION, NULL,
    { { 'C', GDK_CONTROL_MASK },
      { 'C', GDK_CONTROL_MASK }
    },
    NULL },
  { "Move to...", fileselector_for_move_callback, SENSITIVE_SELECTION, NULL,
    { { 'V', GDK_CONTROL_MASK },
      { 'R', GDK_CONTROL_MASK }
    },
    NULL },
  { "Map ops.", NULL, SENSITIVE_SELECTION, NULL,
    { UNBOUND,
      UNBOUND
    },
    &thumbnail_panel_selection_map_menu },
#ifdef GIMP_HAVE_PARASITES
  { "Show comment", show_comment_callback, SENSITIVE_SELECTION, NULL,
    { { 'C', GDK_SHIFT_MASK },
      { 'C', GDK_SHIFT_MASK }
    },
    NULL },
#endif
  { "Delete the thumbnail files", purge_selected_thumbnail_file_callback, SENSITIVE_SELECTION, NULL,
    { { 'T', GDK_CONTROL_MASK },
      { 'T', GDK_MOD1_MASK }
    },
    NULL },
  MENU_SEPARATOR,
  { "Root menu", NULL, FALSE, NULL,
    { UNBOUND,
      UNBOUND
    },
    &thumbnail_panel_root_menu },
  MENU_SEPARATOR,
  { "Delete the selected files", delete_callback, SENSITIVE_SELECTION, NULL,
    { { 'X', GDK_CONTROL_MASK },
      { 'D', GDK_MOD1_MASK }
    },
    NULL }
};

image_command_table
image_hidden_commands [] =
{
  { "Forward image", forward_callback, FALSE, NULL,
    { { 'F', 0 },
      { 'F', GDK_CONTROL_MASK }
    },
    NULL },
  { "Backward image", backward_callback, FALSE, NULL,
    { { 'B', 0 },
      { 'B', GDK_CONTROL_MASK }
    },
    NULL },
  { "Down image", next_callback, FALSE, NULL,
    { { 'D', 0 },
      { 'N', GDK_CONTROL_MASK }
    },
    NULL },
  { "Up image", prev_callback, FALSE, NULL,
    { { 'U', 0 },
      { 'P', GDK_CONTROL_MASK }
    },
    NULL },
  { "Select then go forward", select_and_forward_callback, FALSE, NULL,
    { { ' ', GDK_CONTROL_MASK },
      { ' ', GDK_CONTROL_MASK }
    },
    NULL },
  { "Jump to subdirectory1", jump_to_subdir1_callback, FALSE, NULL,
    { { '1', 0 },
      { '1', 0 }
    },
    NULL },
  { "Jump to subdirectory2", jump_to_subdir2_callback, FALSE, NULL,
    { { '2', 0 },
      { '2', 0 }
    },
    NULL },
  { "Jump to subdirectory3", jump_to_subdir3_callback, FALSE, NULL,
    { { '3', 0 },
      { '3', 0 }
    },
    NULL },
  { "Jump to subdirectory4", jump_to_subdir4_callback, FALSE, NULL,
    { { '4', 0 },
      { '4', 0 }
    },
    NULL },
  { "Jump to subdirectory5", jump_to_subdir5_callback, FALSE, NULL,
    { { '5', 0 },
      { '5', 0 }
    },
    NULL },
  { "Jump to subdirectory6", jump_to_subdir6_callback, FALSE, NULL,
    { { '6', 0 },
      { '6', 0 }
    },
    NULL },
  { "Jump to subdirectory7", jump_to_subdir7_callback, FALSE, NULL,
    { { '7', 0 },
      { '7', 0 }
    },
    NULL },
  { "Jump to subdirectory8", jump_to_subdir8_callback, FALSE, NULL,
    { { '8', 0 },
      { '8', 0 }
    },
    NULL },
  { "Jump to subdirectory9", jump_to_subdir9_callback, FALSE, NULL,
    { { '9', 0 },
      { '9', 0 }
    },
    NULL }
};
#undef MENU_SEPARATOR

#define DOCUMENT_SEPARATOR	{ NULL, NULL, NULL, FALSE }
#define CENTERING	FALSE
#define ALIGN_LEFT	TRUE

help_table
help_document [] =
{
  { NULL, "Gimp Users' Another SHell", NULL, CENTERING},
  { NULL, GUASH_VERSION, NULL, CENTERING},
  DOCUMENT_SEPARATOR,
  { "ACTION", "CONDITION", "COMMAND", CENTERING },
  /* -------------------- */
  { "Left-click", "on unselected image file",
    "Select only the image file", ALIGN_LEFT },
  { "Left-click", "on selected image file",
    "Open the selected image files", ALIGN_LEFT },
  { "Left-click", "on directory",
    "Jump to the directory", ALIGN_LEFT },
  { "Shift + Left-click", "on image file",
    "Add/delete the file to/from selection", ALIGN_LEFT },
  { "Control + Left-click", "on directory & active selection",
    "Copy selected files to the directory", ALIGN_LEFT },
  { "Shift + Left-click", "on directory & active selection",
    "Move selected files to the directory", ALIGN_LEFT },
  { "Middle-click", NULL,
    "Go to the next page of panel", ALIGN_LEFT },
  { "Shift + Middle-click", NULL,
    "Go to the pevious page of panel", ALIGN_LEFT },
  { "Right-click", "no selection",
    "Open root menu", ALIGN_LEFT },
  { "Right-click", "active selection",
    "Open selection menu", ALIGN_LEFT },
  { "Shift + Right-click", NULL,
    "Open root menu", ALIGN_LEFT },
  { "Drag then Stay", "on directory",
    "Drag into the directory", ALIGN_LEFT },
  DOCUMENT_SEPARATOR,
  { NULL, "You can set options through your gimprc file like:", NULL, CENTERING },
  { NULL, "(guash-option-string \"NEIXNA\")   ; default is \"CGIXNM\"", NULL, CENTERING },
  { NULL, "Each character means (do Confirm/Not), (key-binding Gimp/Emacs), (show Images/Files), ", NULL, CENTERING },
  { NULL, "(thumbnail Xv/Guash/Unsave), (sort Name/Date), (update Automatically/Manually) respectively.", NULL, CENTERING }
};

#undef DOCUMENT_SEPARATOR
#undef CENTERING
#undef ALIGN_LEFT

void query(void);

GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,				/* init_proc  */
  NULL,				/* quit_proc */
  query,			/* query_proc */
  run,				/* run_proc */
};

MAIN ()

static void
query ()
{
 /* NOTE: `[] = {}' is illegal on MIPS C++ */

  static GimpParamDef eargs [] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
    { GIMP_PDB_STRING, "directory_name", "directory name used as the default directory" },
  };
  static GimpParamDef pargs [] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
    { GIMP_PDB_IMAGE, "image", "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable", "drawable (unused)" },
  };
  static GimpParamDef *return_vals = NULL;

  static GimpParamDef sargs [] =
  {
    { GIMP_PDB_STRING, "file_name", "the filename that defines selection for script-fu"},
  };
  static GimpParamDef srets [] =
  {
    { GIMP_PDB_STRING, "file_name", "the filename that defines selection for script-fu"},
  };

  static int neargs = sizeof (eargs) / sizeof (eargs[0]);
  static int nreturn_vals = 0;
  static int npargs = sizeof (pargs) / sizeof (pargs[0]);
  static int nsargs = sizeof (sargs) / sizeof (sargs[0]);
  static int nsrets = sizeof (srets) / sizeof (srets[0]);

#ifdef GUASH_I18N
  INIT_I18N ();
#endif
  gimp_install_procedure (PLUG_IN_NAME,
			  "Thumbnail-based directory browser",
			  "Thumbnail-based directory browser",
			  "Shuji Narazaki <narazaki@gimp.org>",
			  "Shuji Narazaki",
			  "1997-1999",
			  MENU_PATH_AS_EXTENSION,
			  "",
			  GIMP_EXTENSION,
			  neargs, nreturn_vals,
			  eargs, return_vals);
  gimp_install_procedure (PLUG_IN_NAME_SUB,
			  "Thumbnail-based directory browser",
			  "Thumbnail-based directory browser",
			  "Shuji Narazaki <narazaki@gimp.org>",
			  "Shuji Narazaki",
			  "1997-1999",
			  MENU_PATH_AS_PLUG_IN,
			  "",
			  GIMP_PLUGIN,
			  npargs, nreturn_vals,
			  pargs, return_vals);
  gimp_install_procedure (PLUG_IN_NAME_FOR_SELECTION,
			  "Return the name of the file that defines %guash-selection",
			  "Return the name of the file that defines %guash-selection",
			  "Shuji Narazaki <narazaki@gimp.org>",
			  "Shuji Narazaki",
			  "1997-1999",
		          NULL,
			  "",
			  GIMP_PLUGIN,
			  nsargs, nsrets,
			  sargs, srets);
}

static void
run (char	*name,
     int	nparams,
     GimpParam	*param,
     int	*nreturn_vals,
     GimpParam	**return_vals)
{
  GimpParam	*values;
  GimpPDBStatusType	status = GIMP_PDB_EXECUTION_ERROR;
  GimpRunModeType	run_mode;

  DEBUGBLOCK (N_("run\n"));

  if (strcmp (name, PLUG_IN_NAME_FOR_SELECTION) == 0)
    {
      GCHAR	tmp[128];
      values = g_new (GimpParam, 2);
      *nreturn_vals = 2;
      *return_vals = values;

      strcpy (tmp, "/tmp/guash0000.scm");
      /* gimp_get_data (PLUG_IN_NAME_FOR_SELECTION, &tmp); */
      values[0].type = GIMP_PDB_STATUS;
      values[0].data.d_status = GIMP_PDB_SUCCESS;
      values[1].type = GIMP_PDB_STRING;
      strcpy (tmp, param[0].data.d_string);
      values[1].data.d_string = g_strdup (tmp);
      DPRINT (N_("delete %s\n"), tmp);
      os_delete_file (tmp);
      RETURN;
    }

  values = g_new (GimpParam, 1);
  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  DPRINT (N_("guash is just started.\n"));
  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
    case GIMP_RUN_WITH_LAST_VALS:
      SET_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P);
      SET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
      SET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
      SET_ATTRIBUTE (&VAL, SORT_BY_NAME_P);
      VAL.mapping_command[0] = VAL.last_dir_name[0] = 0;
      gimp_get_data (PLUG_IN_NAME, &VAL);

      if (strcmp (name, PLUG_IN_NAME_SUB) == 0)
	{
	  GCHAR	*str = guash_get_image_filename (param[1].data.d_image);

	  if (str)
	    {
	      GCHAR	*dir = pathname_get_directoryname (str);

	      if (dir)
		{
		  strcpy (VAL.last_dir_name, dir);
		  g_free (dir);
		}
	      g_free (str);
	    }
	}

      directory_cache_table = g_hash_table_new ((GHashFunc) g_str_hash,
						(GCompareFunc) g_str_equal);
      if (0 < strlen (VAL.last_dir_name))
	os_file_change_current_directory (VAL.last_dir_name);

      dir_icon = image_buffer_new_with_header (dir_icon_data,
					       dir_icon_width,
					       dir_icon_height);
      pdir_icon = image_buffer_new_with_header (pdir_icon_data,
						pdir_icon_width,
						pdir_icon_height);
      file_icon = image_buffer_new_with_header (file_icon_data,
						file_icon_width,
						file_icon_height);

      DPRINT (N_("icons are set\n"));
#ifdef GIMP_VERSION
      /* Let's set rational options of ps file handler for guash. */
      {
	GimpParam*		return_vals;
	gint		retvals;

	return_vals = gimp_run_procedure ("file_ps_load_setargs",
					  &retvals,
					  GIMP_PDB_INT32, 75, /* resolution */
					  GIMP_PDB_INT32, THUMBNAIL_WIDTH,
					  GIMP_PDB_INT32, THUMBNAIL_HEIGHT,
					  GIMP_PDB_INT32, 1, /* use BBox */
					  GIMP_PDB_STRING, "1", /* only 1 page */
					  GIMP_PDB_INT32, 7, /* automatic color */
					  GIMP_PDB_INT32, 1, /* text alpha */
					  GIMP_PDB_INT32, 1,
					  GIMP_PDB_END);
	gimp_destroy_params (return_vals, retvals);
      }
#endif
      DPRINT (N_("open dialog\n"));
#ifdef GUASH_I18N
      INIT_I18N_UI();
#endif
      DIALOG ();
      DPRINT (N_("success to close the dialog\n"));

      gimp_displays_flush();
      if (strcmp (name, PLUG_IN_NAME) == 0)
	gimp_set_data (PLUG_IN_NAME, &VAL, sizeof (VALS));
      status = GIMP_PDB_SUCCESS;
      break;
    case GIMP_RUN_NONINTERACTIVE:
      break;
    }

#if 0
  g_hash_table_destroy (directory_cache_table);

  image_buffer_free (banner);
  image_buffer_free (dir_icon);
  image_buffer_free (pdir_icon);
  image_buffer_free (file_icon);

  g_free (inhibit_suffix_table);

  DPRINT (N_("success to free memory chunks\n"));
#endif
  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  DPRINT ("bye!\n");
  DEBUGEND;
}

static guchar *
indexed_to_rgb (guchar data, guchar *dest)
{
  guchar *color = thumbnail_colormap + (data * 3);

  *dest++ = *color++;
  *dest++ = *color++;
  *dest++ = *color;

  return dest;
}

/*
 * guash classes for packing misc functions
 */
static void
guash_build_thumbnail_colormap ()
{
  gint		i;
  guchar	*ptr = thumbnail_colormap;

  for (i = 0; i < 256; i++)
    {
      *ptr++ = (((i >> 5) & 0x07) * 255) / 7;
      *ptr++ = (((i >> 2) & 0x07) * 255) / 7;
      *ptr++ = (((i >> 0) & 0x03) * 255) / 3;
    }
}

static void
guash_build_inhibit_suffix_table (GCHAR *buffer)
{
  GCHAR	*ptr = buffer;
  GCHAR	**chunk;
  gint	chunk_index = 0;
  gint	chunk_size = 8;

  DEBUGBLOCK (N_("guash_build_inhibit_suffix_table (\"%s\")\n"), buffer);

  chunk = g_new (GCHAR *, chunk_size);

  while (*ptr != 0)
    {
      gint	index = 0;
      GCHAR	*new_str;

      while ((*(ptr + index) != ':') && (*(ptr + index) != 0))
	index++;

      if (0 < index)
	{
	  new_str = g_new (GCHAR, index + 1); /*  end of str */
	  g_memmove (new_str, ptr, index);
	  new_str[index] = 0;

	  if (chunk_size <= chunk_index)
	    {
	      DPRINT (N_("Exapnd chunk(%s) size: %d..."), chunk[chunk_index -1], chunk_size);
	      chunk_size *= 2;
#ifdef GIMP_HAVE_PARASITES
	      /* This condition is not true one. I'd like to check glib
		 version. */
	      chunk = g_renew (GCHAR *, chunk, chunk_size);
#else
	      chunk = g_realloc ((gpointer) chunk,
				 chunk_size * sizeof (GCHAR *));
#endif
	      DPRINT (N_("done\n"));
	    }
	  chunk[chunk_index++] = new_str;
	}
      if (*(ptr + index) == 0)
	break;
      else
	ptr += index + 1;
    }

  inhibit_suffix_table = chunk;
  num_inhibit_suffix = chunk_index;

  RETURN;
}

static gint
guash_change_current_directory (GCHAR *pathname, Thumbnail *thumbnail)
{
  _DEBUGBLOCK (N_("guash_change_directory (\"%s\")\n"), pathname);

  if (-1 == os_file_change_current_directory (pathname))
    {
      GCHAR	tmp[LINE_BUF_SIZE];

      if (thumbnail != NULL)
	{
	  cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);
	  SET_ATTRIBUTE (thumbnail, DELETED_P);
	  guash_update_cwd_cache (GC);
	}
      sprintf (tmp, "Abort: %s does not exist now", pathname);
      thumbnail_panel_set_info (tmp);
      RETURN FALSE;
    }
  else
    {
      guash_update_cwd_cache (REDRAW);
      RETURN TRUE;
    }
}

static gint
guash_confirm_operation (GCHAR *operation_phrase)
{
  gint	flag = TRUE;

  if (! selection_is_active ())
    return flag;

  G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
						    cwd_cache->selection_top),
			   SELECTED_P));

  if (HAS_ATTRIBUTE (&VAL, CONFIRM_P))
    {
      GCHAR buffer [LINE_BUF_SIZE];

      if (selection_length () == 1)
	{
	  Thumbnail	*selected;

	  selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);
	  sprintf (buffer, _(" %s the selected image: %s? \n\
 Guash is NO WARRANTY program. "),
		   operation_phrase,
		   selected->name);
	}
      else
	{
	  sprintf (buffer, _(" %s the selected %d images? \n\
 Guash is NO WARRANTY program. "),
		   operation_phrase,
		   selection_length ());
	}

      if (! gtkW_confirmor_dialog (TRUE, buffer, FALSE))
	flag = FALSE;
    }

  return flag;
}

static gint
guash_discard_events ()
{
  gint		invalids = FALSE;
  gint		redraw = FALSE;
  GdkEvent	*e;

  DEBUGBLOCK (N_("guash_discard_events\n"));
  /*
   * Tue May 12 20:49:27 1998
   * gtk+ FAQ uses gtk_events_pending (), but it returns 1 anytime.
   * On the other hand, gdk_events_pending () returns zero if no events!
   * That's what I want!
   */
  while (gdk_events_pending ())
    {
      if ((e = gdk_event_get ()) != NULL)
	{
	  switch (e->type)
	    {
	    case GDK_BUTTON_PRESS:
	    case GDK_2BUTTON_PRESS:
	    case GDK_3BUTTON_PRESS:
	    case GDK_KEY_PRESS:
	      invalids = TRUE;
	      gdk_event_free (e);
	      break;
	    case GDK_EXPOSE:
	      redraw = TRUE;
	      gdk_event_free (e);
	    default:
	      gtk_main_iteration ();
	      break;
	    }
	}
    }
  if (redraw)
    {
      display_request_redraw ();
      display_request_set_handler ();
    }

  RETURN invalids;
}

static GCHAR *
guash_generate_unix_command (GCHAR *template, GCHAR *directory, GCHAR *filename)
{
  GCHAR	*new = NULL, *tmp;
  gint	index = -1;
  gint	directory_p = FALSE;
  gint	i = 0;

  for (tmp = template; *tmp; tmp++, i++)
    {
      if ((*tmp == '{') && (*(tmp + 1) == '}'))
	{
	  directory_p = FALSE;
	  index = i;
	  break;
	}
      if ((*tmp == '%') && (*(tmp + 1) == '%'))
	{
	  directory_p = TRUE;
	  index = i;
	  break;
	}
    }
  if (-1 < index)
    {
      if (directory_p)
	new = g_malloc (strlen (template) + strlen (directory) + 1);
      else
	new = g_malloc (strlen (template)
			+ strlen (directory) + strlen (filename) + 1);
      i = index;
      g_memmove (new, template, i);
      if (directory_p)
	{
	  g_memmove (new + i, directory, strlen (directory));
	  i += strlen (directory);
	}
      else
	{
	  g_memmove (new + i, filename, strlen (filename));
	  i += strlen (filename);
	}
      g_memmove (new + i, template + index + 2, strlen (template) - index - 2);
      i += strlen (template) - index - 2;
      new[i] = 0;

      tmp = guash_generate_unix_command (new, directory, filename);
      g_free (new);
      return tmp;
    }
  else
    {
      return g_strdup (template);
    }
}

static GCHAR *
guash_get_image_filename (gint image_id)
{
  GCHAR	*path = gimp_image_get_filename (image_id);

  if (pathname_get_last_separator_index (path) < 0)
    {
      GCHAR	tmp[PATH_LENGTH];
      gint	index;

      os_file_get_current_directory (tmp);
      index = strlen (tmp);
      tmp[index] = G_DIR_SEPARATOR;
      g_memmove (tmp + index + 1, path, strlen (path) + 1);
      g_free (path);
      path = g_strdup (tmp);
    }
  return path;
}

#if 1
static Thumbnail *
guash_build_thumbnail_from_gimage (gint32 i_id, GCHAR *pathname)
{
  gint bytes = 3;
  gint	width, height;
  gint	new_width, new_height;
  guchar *data = NULL;
  GCHAR		info[256];
  GCHAR		image_type[16];
  GimpParam*	return_vals;
  gint		retvals;
  GimpImageType	layer_type = GIMP_RGBA_IMAGE;

  DEBUGBLOCK (N_("guash_build_thumbnail_from_gimage (%d, \"%s\")\n"),
	      i_id, pathname);

  if (gimp_image_base_type (i_id) != GIMP_RGB)
    {
      if (gimp_image_base_type (i_id) == GIMP_INDEXED)
	{
	  layer_type = GIMP_INDEXEDA_IMAGE;
	  strcpy (image_type, "Indexed file");
	}
      else
	{
	  strcpy (image_type, "Gray file");

	  DPRINT (N_("convert image to RGB\n"));

	  return_vals = gimp_run_procedure ("gimp_convert_rgb",
					    &retvals,
					    GIMP_PDB_IMAGE, i_id,
					    GIMP_PDB_END);
	  gimp_destroy_params (return_vals, retvals);
	}
    }
  else
    {
      strcpy (image_type, "RGB file");
      DPRINT (N_("the image is RGB type\n"));
    }

  new_width = width = gimp_image_width (i_id);
  new_height = height = gimp_image_height (i_id);

  if (THUMBNAIL_HEIGHT < new_height)
    {
      new_width = new_width * THUMBNAIL_HEIGHT / new_height;
      new_height = THUMBNAIL_HEIGHT;
    }
  if (THUMBNAIL_WIDTH < new_width)
    {
      new_height = new_height * THUMBNAIL_WIDTH / new_width;
      new_width = THUMBNAIL_WIDTH;
    }
  if (new_width < 2) new_width = 2;
  if (new_height < 2) new_height = 2;
  
  {
    gint32	d_id;

    /* some loaders (tiff, bmp) generate strange thumbnail first.
       Indeed, load->get_thumnail_data sequence seems to return a broken data.
       Thus I must do a PDB call here in order to purge the broken data anyway.
       Thu Nov 18 17:59:23 1999
    */
    d_id = gimp_image_get_active_layer (i_id);
    if (gimp_layer_is_floating_selection (d_id))
      {
	/* I love gimp_floating_sel_to_layer very much,
	   'cause It calls drawable_invalidate_preview!
	*/
	return_vals = gimp_run_procedure ("gimp_floating_sel_to_layer",
					  &retvals,
					  GIMP_PDB_LAYER, d_id,
					  GIMP_PDB_END);
	gimp_destroy_params (return_vals, retvals);
      }
    else
      {
	gint *layers = NULL;
	gint nlayers;
	gint index;
	gint tmp;
	GimpLayerModeEffects mode = GIMP_NORMAL_MODE;

	layers = gimp_image_get_layers (i_id, &nlayers);

	for (index = nlayers - 1; 0 <= index; index--)
	  if (gimp_layer_get_visible (layers[index])
	      && (gimp_layer_get_mode (layers[index]) == GIMP_NORMAL_MODE))
	    break;

	if (index < 0)
	  for (index = nlayers -1; 0 <= index; index--)
	    if (gimp_layer_get_visible (layers[index]))
	      {
		mode = gimp_layer_get_mode (layers[index]);
		gimp_layer_set_mode (layers[index], GIMP_NORMAL_MODE);
		break;
	      }
	if (index < 0)
	  {
	    index = -1;
	    gimp_layer_set_visible (layers[0], TRUE);
	    mode = gimp_layer_get_mode (0); 
	  }

	g_free (layers);

	tmp = gimp_layer_new (i_id, "tmp", 10, 10, layer_type, 0, GIMP_NORMAL_MODE);
	gimp_image_add_layer (i_id, tmp, MAX (index - 1, -1));

	/* I love gimp_image_merge_down very much!!! It emits RESTRUCTURE!!!
	   NO MORE image_flatten !!!
	 */
	return_vals = gimp_run_procedure ("gimp_image_merge_down",
					  &retvals,
					  GIMP_PDB_IMAGE, i_id,
					  GIMP_PDB_LAYER, tmp,
					  GIMP_PDB_INT32, GIMP_EXPAND_AS_NECESSARY,
					  GIMP_PDB_END);
	gimp_layer_set_mode (return_vals[1].data.d_layer, mode);
	gimp_destroy_params (return_vals, retvals);
      }
  }
  data = gimp_image_get_thumbnail_data ((gint32) i_id,
					&new_width,
					&new_height,
					&bytes);

  image_buffer_resize (the_loaded_data->image, new_width, new_height, 3);
  {
    gint i;
    guchar *dest = the_loaded_data->image->data;

    if (bytes == 4)
      for (i = 0; i < new_width * new_height * bytes; i += 4)
	{
	  guchar opacity = data[i+3];

	  if (opacity == 255)
	    {
	      *dest++ = data[i];
	      *dest++ = data[i+1];
	      *dest++ = data[i+2];
	    }
	  else if (opacity == 0)
	    {
	      *dest++ = 255;
	      *dest++ = 255;
	      *dest++ = 255;
	    }
	  else
	    {
	      gdouble bg = (255 - opacity) * 255.0;

	      *dest++ = (data[i] * opacity + bg) / 255.0;
	      *dest++ = (data[i+1] * opacity + bg) / 255.0;
	      *dest++ = (data[i+2] * opacity + bg) / 255.0;
	    }
	}
    else if (bytes == 3)
      for (i = 0; i < new_width * new_height * bytes; i++)
	*dest++ = *data++;
    else
      g_error ("execution error: guash got unnown data from gimp_image_get_thumbnail_data\n");
  }
  g_free (data);

  if (the_loaded_data->name)
    {
      g_free (the_loaded_data->name);
      the_loaded_data->name = NULL;
    }
  the_loaded_data->name = g_strdup (pathname_get_basename (pathname));

  if (the_loaded_data->info)
    {
      g_free (the_loaded_data->info);
      the_loaded_data->info = NULL;
    }
  sprintf (info, "%dx%d %s (%d bytes)",
	   width, height,
	   image_type, os_file_size (pathname));
  {
    GCHAR	tmp[PATH_LENGTH];
#ifdef GIMP_HAVE_PARASITES
    GimpParasite	*para = NULL;

    para = gimp_image_parasite_find (i_id, "gimp-comment");
    if ((para != NULL) && (0 < para->size))
      {
	GCHAR	comment[242];
	GCHAR	*src = (GCHAR *) para->data;
	gint	i;

	/* maybe newline in info field in xv thumbnail causes a trouble. */
	for (i = 0; i < 241; i++)
	  {
	    if (!(comment[i] = *src++))
	      break;
	    if (comment[i] == '\n')
	      {
		comment[i] = 0;
		break;
	      }
	  }
	DPRINT (N_("found gimp-comment parasite: %s\n"), comment);
	sprintf (tmp, "%s %s%c %s", the_loaded_data->name, info,
		 COMMENT_DELIMITOR, comment);
      }
    else
      sprintf (tmp, "%s%c %s", the_loaded_data->name, COMMENT_DELIMITOR, info);
    if (para != NULL)
      gimp_parasite_free (para);
#else
    sprintf (tmp, "%s%c %s", the_loaded_data->name, COMMENT_DELIMITOR, info);
#endif
    the_loaded_data->info = g_strdup (tmp);
  }
  
  RETURN the_loaded_data;
}
#else
static Thumbnail *
guash_build_thumbnail_from_gimage (gint32 i_id, GCHAR *pathname)
{
  gint32 	d_id;
  gint		new_width, new_height;
  gint		width, height;
  GCHAR		image_type[16];
  GimpParam*	return_vals;
  gint		retvals;
  GCHAR		info[256];

  DEBUGBLOCK (N_("guash_build_thumbnail_from_gimage (%d, \"%s\")\n"),
	      i_id, pathname);

  if (gimp_image_base_type (i_id) != GIMP_RGB)
    {
      if (gimp_image_base_type (i_id) == GIMP_INDEXED)
	strcpy (image_type, "Indexed file");
      else
	strcpy (image_type, "Gray file");

      DPRINT (N_("convert image to RGB\n"));

      return_vals = gimp_run_procedure ("gimp_convert_rgb",
					&retvals,
					GIMP_PDB_IMAGE, i_id,
					GIMP_PDB_END);
      gimp_destroy_params (return_vals, retvals);
    }
  else
    {
      strcpy (image_type, "RGB file");
      DPRINT (N_("the image is RGB type\n"));
    }

  new_width = width = gimp_image_width (i_id);
  new_height = height = gimp_image_height (i_id);

  if (THUMBNAIL_HEIGHT < new_height)
    {
      new_width = new_width * THUMBNAIL_HEIGHT / new_height;
      new_height = THUMBNAIL_HEIGHT;
    }
  if (THUMBNAIL_WIDTH < new_width)
    {
      new_height = new_height * THUMBNAIL_WIDTH / new_width;
      new_width = THUMBNAIL_WIDTH;
    }
  if (new_width < 2) new_width = 2;
  if (new_height < 2) new_height = 2;

  {
    guchar	tr[3] = BASE_COLOR;
    guchar	bg[3];

    gimp_palette_get_background (bg, bg + 1, bg + 2);
    gimp_palette_set_background (tr[0], tr[1], tr[2]);
    gimp_image_flatten (i_id);
    DPRINT (N_("success to flatten image"));
    gimp_palette_set_background (bg[0], bg[1], bg[2]);
  }

  d_id = gimp_image_get_active_layer (i_id);
  G_ASSERT (0 <= d_id);

  if ((width != new_width) || (height != new_height))
    {
      gimp_layer_scale (d_id, new_width, new_height, 0);
      DPRINT (N_("resized to thumbnail size.\n"));
    }

  sprintf (info, "%dx%d %s (%d bytes)",
	   width, height,
	   image_type, os_file_size (pathname));
  gimp_layer_set_name (d_id, info);

  if (the_loaded_data->name)
    {
      g_free (the_loaded_data->name);
      the_loaded_data->name = NULL;
    }
  the_loaded_data->name = g_strdup (pathname_get_basename (pathname));

  if (the_loaded_data->info)
    {
      g_free (the_loaded_data->info);
      the_loaded_data->info = NULL;
    }
  {
    GCHAR	tmp[PATH_LENGTH];
#ifdef GIMP_HAVE_PARASITES
    GimpParasite	*para = NULL;

    para = gimp_image_parasite_find (i_id, "gimp-comment");
    if ((para != NULL) && (0 < para->size))
      {
	GCHAR	comment[242];
	GCHAR	*src = (GCHAR *) para->data;
	gint	i;

	/* maybe newline in info field in xv thumbnail causes a trouble. */
	for (i = 0; i < 241; i++)
	  {
	    if (!(comment[i] = *src++))
	      break;
	    if (comment[i] == '\n')
	      {
		comment[i] = 0;
		break;
	      }
	  }
	DPRINT (N_("found gimp-comment parasite: %s\n"), comment);
	sprintf (tmp, "%s %s%c %s", the_loaded_data->name, info,
		 COMMENT_DELIMITOR, comment);
      }
    else
      sprintf (tmp, "%s%c %s", the_loaded_data->name, COMMENT_DELIMITOR, info);
    if (para != NULL)
      parasite_free (para);
#else
    sprintf (tmp, "%s%c %s", the_loaded_data->name, COMMENT_DELIMITOR, info);
#endif
    the_loaded_data->info = g_strdup (tmp);
  }

  image_buffer_copy_from_drawable (the_loaded_data->image, d_id);

  RETURN the_loaded_data;
}
#endif

/* if thumbnail_p is non-FALSE, check only thumbnail */
static Thumbnail *
guash_get_image_from_file (GCHAR *pathname, gint thumbnail_p)
{
  GimpParam*	return_vals;
  gint		retvals;
  gint32	i_id;

  _DEBUGBLOCK (N_("guash_get_image_from_file (\"%s\", %d)\n"),
	      pathname, thumbnail_p);
  G_ASSERT (*pathname == G_DIR_SEPARATOR);

  if (thumbnail_p)		/* thumbnail file */
    pathname = pathname_build_thumbnail_filename (pathname);

  DPRINT (N_("the file to try to load is \"%s\"\n"), pathname);

  if ((os_file_kind (pathname, TRUE)) == NOT_EXIST)
    {
      DPRINT (N_("%s is not found\n"), pathname);
      RETURN NULL;
    }

  if (thumbnail_p)
    {
      Thumbnail *thumb = load_xvpict_image (pathname);

      DPRINT (N_("cache is%s found\n"), (thumb == NULL) ? " not" : "");
      RETURN thumb;
    }

  return_vals = gimp_run_procedure ("gimp_file_load",
				    &retvals,
				    GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE,
				    GIMP_PDB_STRING, pathname,
				    GIMP_PDB_STRING, pathname,
				    GIMP_PDB_END);
  if (return_vals[0].data.d_status != GIMP_PDB_SUCCESS)
    {
      gimp_destroy_params (return_vals, retvals);

      DPRINT (N_("fail to load (maybe non-image file)\n"));
      RETURN NULL;
    }
  else
    {
      i_id = return_vals[1].data.d_image;

      gimp_destroy_params (return_vals, retvals);
    }

  DPRINT (N_("seems to success to load from the file\n"));

  /* Some file-loader returns STATUS_SUCCESS even if it fails to load !!! */
  if ((i_id < 0) || (gimp_image_get_active_layer (i_id) < 0))
    {
      DPRINT (N_("failed to loading (i_id:%d, active_layer:%d)\n"),
		 i_id, gimp_image_get_active_layer (i_id));
      if (0 <= i_id)
	gimp_image_delete (i_id);
      RETURN NULL;
    }
  DPRINT (N_("really success to load from the file\n"));
  gimp_image_undo_disable (i_id);
  guash_build_thumbnail_from_gimage (i_id, pathname);
  gimp_image_delete (i_id);

  RETURN the_loaded_data;
}

static directory_cache *
guash_lookup_directory_cache (GCHAR *dir)
{
  return (directory_cache *) g_hash_table_lookup (directory_cache_table,
						  (gpointer) dir);
}

static void
guash_parse_gimprc ()
{
  GCHAR buf[LINE_BUF_SIZE];
  gint	tmp = 0;

  __DEBUGBLOCK (N_("guash_parse_gimprc\n"));

  gtkW_parse_gimprc_string ("guash-option-string", buf);
  if (strlen (buf) == 6)
    {
      GCHAR	*ptr = buf;

#define c_l_eq_p_(C, c)	((('a' - 'A') | (C)) == (c))
      SET_TO_ATTRIBUTE (&VAL, CONFIRM_P, c_l_eq_p_ (*ptr++, 'c'));
      binding_style = c_l_eq_p_ (*ptr++, 'e') ? BINDING_EMACS : BINDING_GIMP;
      SET_TO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P, (! c_l_eq_p_ (*ptr++, 'f')));
      SET_TO_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P, (! c_l_eq_p_ (*ptr, 'u')));
/*
 *    o an EXPERIMENTAL 16bit color thumbnail format. NO DOCUMENT.
 *    NO COMPRESSION (the size reaches to 10KB!)  THE FORMAT MIGHT BE
 *    MODIFIED/DISCARED IN FUTURE RELEASE.
 */
      SET_TO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P, c_l_eq_p_ (*ptr++, 'g'));
      SET_TO_ATTRIBUTE (&VAL, SORT_BY_NAME_P, (! c_l_eq_p_ (*ptr++, 'd')));
      SET_TO_ATTRIBUTE (&VAL, MONITOR_P, c_l_eq_p_ (*ptr, 'a'));
#undef c_l_eq_p_
    }
  else if (0 < strlen (buf))
    g_warning ("The length of guash-option-string is not 6. Ignored.");

  gtkW_parse_gimprc_string ("guash-inhibit-suffix", buf);
  if (*buf == 0)
    strcpy (buf, GUASH_INHIBIT_SUFFIX_TABLE_DEFAULT);
  guash_build_inhibit_suffix_table (buf);

  gtkW_parse_gimprc_string ("guash-mapping-command", buf);
  if (*buf == 0)
    strcpy (buf, MAPPING_COMMAND_DEFAULT);
  if ((VAL.mapping_command == NULL) || strlen (VAL.mapping_command) == 0)
    strcpy (VAL.mapping_command, buf);

  tmp = gtkW_parse_gimprc_gint ("guash-max-images-in-dir",
				directory_cache_max_images);
  if (0 < tmp)
    directory_cache_max_images = CLAMP (tmp, 1, 2000);
  else
    directory_cache_max_images = -1;

  tmp = 0;
  tmp = gtkW_parse_gimprc_gint ("guash-directory-history-length",
				directory_cache_table_max_size);
  if (0 <= tmp)
    directory_cache_table_max_size = CLAMP (tmp, 1, 16);
  else
    directory_cache_table_max_size = -1;

  directory_monitor_interval = DIRECTORY_MONITOR_INTERVAL_DEFAULT * 1000;
  tmp = 0;
  tmp = gtkW_parse_gimprc_gint ("guash-directory-monitor-interval", tmp);
  if (0 < tmp)
    directory_monitor_interval = CLAMP (tmp, 1, 200) * 1000;

  gtkW_parse_gimprc_string ("temp-path", buf);
  guash_tmp_dir = *buf ? g_strdup (buf) : "/tmp";
  if (guash_tmp_dir[strlen (guash_tmp_dir) - 1] == G_DIR_SEPARATOR)
    guash_tmp_dir[strlen (guash_tmp_dir) - 1] = 0;

  RETURN;
}

static void
guash_purge_subdirectory_information (GCHAR *dirname)
{
  g_hash_table_foreach (directory_cache_table,
			directory_cache_table_purge_subdirectory,
			(gpointer) dirname);
}

static void
guash_set_fileselector_last_value (GCHAR *pathname)
{
  if (os_file_kind (pathname, TRUE) == DIRECTORY)
    {
      strcpy (fileselector_last_pathname, pathname);
    }
  else
    {
      gint	len;

      strcpy (fileselector_last_pathname,
	      pathname_get_directoryname (pathname));
      len = strlen (fileselector_last_pathname);
      fileselector_last_pathname[len] = G_DIR_SEPARATOR;
      fileselector_last_pathname[len + 1] = 0;
    }
}

static directory_cache *
guash_update_cwd_cache (gint mode)
{
  /* mode is either of
     RESCAN: to build w/o current information
     REDRAW: only to update panel
     GC: to remove unexisting files from panel
  */
  gpointer	ptr = NULL;
  GCHAR		dir[PATH_LENGTH];

  os_file_get_current_directory (dir);

  _DEBUGBLOCK (N_("guash_update_cwd_cache (%d) at %s\n"), mode, dir);

  G_ASSERT (mode == RESCAN || mode == GC || mode == REDRAW);

  if ((ptr = g_hash_table_lookup (directory_cache_table, (gpointer) dir)))
    {
      cwd_cache = (directory_cache *) ptr;

      DPRINT (N_("found directory_cache for %s\n"), dir);

      g_hash_table_foreach (directory_cache_table,
			    directory_cache_table_aging,
			    NULL);
      cwd_cache->birth_index = directory_cache_table_size;
      DPRINT (N_("success to LRU ordering to directory_caches\n"));

      if ((mode == RESCAN) || HAS_ATTRIBUTE (cwd_cache, PURGED_P))
	{
	  RESET_ATTRIBUTE (cwd_cache, PURGED_P);
	  cwd_cache_update ();
	}
      else if (mode == GC)
	{
	  directory_cache_garbage_collect (cwd_cache);
	}
      else
	{
	  if ((HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P)
	       && HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
	      || (HAS_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P)
		  && HAS_NO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
	      || (HAS_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P)
		  && HAS_NO_ATTRIBUTE (&VAL, SORT_BY_NAME_P))
	      || (HAS_NO_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P)
		  && HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P)))
	    cwd_cache_update ();
	  else
	    {
	      if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
		{
		  directory_cache_garbage_collect (cwd_cache);
		  DPRINT (N_("success to garbage collect\n"));
		}
	      else
		{
		  display_request_redraw ();
		}
	    }
	}
    }
  else
    {
      if ((0 < directory_cache_table_max_size) &&
	  (directory_cache_table_max_size <= directory_cache_table_size))
	{
	  directory_cache	*tmp = NULL;

	  DPRINT (N_("recycle directory_cache\n"));
	  /* First, set cwd_cache to NULL. */
	  cwd_cache = NULL;
	  /* Second: map recycler.
	   * Recycler sets one of the oldest entries to cwd_cache
	   * and sets the 1st entry of directory hashtable to tmp. */
	  g_hash_table_foreach (directory_cache_table,
				directory_cache_table_recycler,
				(gpointer) &tmp);
	  if (cwd_cache)	/* recycler worked fine, finding an entry! */
	    {
	      /* Then, delete it from directory_cache_table */
	      g_hash_table_remove (directory_cache_table, cwd_cache->name);
	      /* Finally, set the name as the new key */
	      strcpy (cwd_cache->name, dir);
	      directory_cache_initialize (cwd_cache);
	      cwd_cache->birth_index = directory_cache_table_max_size;
	    }
	  else			/* should not happen! FAILSAFE path */
	    {
	      if (tmp == NULL)
		{
		  printf ("CATASTROPHIC ERROR: CACHE RECYCLE FAILED!!!\n");
		  cwd_cache = directory_cache_new (dir);
		}
	      else
		{
		  printf ("Warning: directory_cache recycle failed (%s)!\n", tmp->name);
		  cwd_cache = tmp;
		  /* tmp is born again as cwd_cache */
		  cwd_cache->birth_index = directory_cache_table_size;
		}
	    }
	}
      else
	{
	  if (0 < directory_cache_table_max_size)
	    directory_cache_table_size++;

	  DPRINT (N_("making a new directory_cache...\n"));
	  cwd_cache = directory_cache_new (dir);
	  DPRINT (N_("making a new directory_cache... done\n"));
	}
      g_hash_table_insert (directory_cache_table,
			   cwd_cache->name,
			   (gpointer) cwd_cache);

      cwd_cache_update ();
    }
  directory_cache_auto_shrink (cwd_cache, FALSE);
  strcpy (VAL.last_dir_name, cwd_cache->name);

  RETURN cwd_cache;
}

static gint
guash_add_entry (GCHAR *pathname, Thumbnail *thumb)
{
  GCHAR			*dirname;
  directory_cache	*dcache;

  DEBUGBLOCK (N_("guash_add_entry (\"%s\", %s_thumb.)\n"), pathname,
	      (thumb ? thumb->name : "no"));

  dirname = pathname_get_directoryname (pathname);
  if ((dcache = guash_lookup_directory_cache (dirname)) == NULL)
    {
      DPRINT (N_("%s was not visted\n"), dirname);
      g_free (dirname);
      DPRINT (N_("done of guash_add_entry\n"));
      RETURN FALSE;
    }
  directory_cache_update_thumbnail_for (dcache, pathname, thumb);
  SET_ATTRIBUTE (dcache, DISORDERED_P);

  g_free (dirname);
  RETURN (cwd_cache == dcache);
}

static gboolean
guash_copy_image_file (GCHAR *filename, GCHAR *newname)
{
  /* FIXME
     UPDATE timestamp!
 */
  DEBUGBLOCK (N_("guash_copy_image_file (\"%s\", \"%s\")\n"),
	      filename, newname);

  if (! guash_validate_src_file (filename))
    {
      DPRINT ("%s is not valid src file.\n", filename);
      RETURN FALSE;
    }

  if (os_file_kind (newname, TRUE) == NOT_EXIST)
    {
      GCHAR	*from_thumbnail_name;
      GCHAR	*to_thumbnail_name;
      mode_t	mode = NEW_DIRECTORY_MODE;

      if (! os_copy_file (filename, newname))
	RETURN FALSE;

      /* copy thumbnail */
      from_thumbnail_name = pathname_build_thumbnail_filename (filename);
      if (os_file_kind (from_thumbnail_name, TRUE) != REGFILE)
	{
	  g_free (from_thumbnail_name);
	  RETURN TRUE;
	}
      to_thumbnail_name = pathname_build_thumbnail_filename (newname);
      {
	GCHAR	*to_dir_name;

	to_dir_name = pathname_get_directoryname (to_thumbnail_name);

	if (os_make_directory (to_dir_name, mode) == -1)
	  if (errno != EEXIST)
	    {
	      g_free (to_dir_name);
	      g_free (to_thumbnail_name);
	      g_free (from_thumbnail_name);
	      RETURN TRUE;
	    }
	if (os_file_kind (to_dir_name, TRUE) != DIRECTORY)
	  {
	    g_free (to_dir_name);
	    g_free (to_thumbnail_name);
	    g_free (from_thumbnail_name);
	    RETURN TRUE;
	  }
	g_free (to_dir_name);
      }
      os_copy_file (from_thumbnail_name, to_thumbnail_name);

      g_free (from_thumbnail_name);
      g_free (to_thumbnail_name);
      RETURN TRUE;
    }
  else
    RETURN FALSE;
}

static gboolean
guash_delete_image_file (GCHAR *filename)
{
  if (! guash_validate_src_file (filename))
    return FALSE;

  return (os_delete_file (filename) == 0);
}

static gboolean
guash_move_image_file (GCHAR *filename, GCHAR *newname)
{
  DEBUGBLOCK (N_("guash_move_image_file (\"%s\", \"%s\")\n"),
	      filename, newname);

  if (! guash_validate_src_file (filename))
    RETURN FALSE;

  if (os_file_kind (newname, TRUE) == NOT_EXIST)
    {
      if (os_rename_file (filename, newname) == 0)
	{
	  GCHAR		*from_thumbnail_name = NULL;
	  GCHAR		*to_thumbnail_name = NULL;
	  GCHAR		*thumbnail_dir = NULL;
	  mode_t	mode = NEW_DIRECTORY_MODE;

	  from_thumbnail_name = pathname_build_thumbnail_filename (filename);

	  if (os_file_kind (from_thumbnail_name, TRUE) != REGFILE)
	    {
	      g_free (from_thumbnail_name);
	      RETURN TRUE;
	    }
	  to_thumbnail_name = pathname_build_thumbnail_filename (newname);
	  thumbnail_dir = pathname_get_directoryname (to_thumbnail_name);

	  if (os_make_directory (thumbnail_dir, mode) == -1)
	    if (errno != EEXIST)
	      {
		g_free (thumbnail_dir);
		g_free (to_thumbnail_name);
		g_free (from_thumbnail_name);
		RETURN TRUE;
	      }
	  if (os_file_kind (thumbnail_dir, TRUE) != DIRECTORY)
	    {
	      g_free (thumbnail_dir);
	      g_free (to_thumbnail_name);
	      g_free (from_thumbnail_name);
	      RETURN TRUE;
	    }
	  g_free (thumbnail_dir);

	  if (os_rename_file (from_thumbnail_name, to_thumbnail_name) == -1)
	    {
	      perror ("rename thumbnail file");
	      printf ("from %s to %s\n", from_thumbnail_name, to_thumbnail_name);
	    }

	  g_free (from_thumbnail_name);
	  g_free (to_thumbnail_name);
	  RETURN TRUE;
	}
      else
	{
	  if (errno == EXDEV)
	    {
	      if (guash_copy_image_file (filename, newname))
		{
		  guash_delete_image_file (filename);
		  RETURN TRUE;
		}
	      else
		RETURN FALSE;
	    }
	  else
	    {
	      perror ("file_move");
	      RETURN FALSE;
	    }
	}
    }
  else
    {
      GString *message, *another_name;
      gboolean	flag = FALSE;

      message = g_string_new ("Warning: ");
      g_string_append (message, newname);
      g_string_append (message, " already exists.\nThe file will have another filename temporally");
      gtkW_message_dialog (TRUE, message->str);
      g_string_free (message, TRUE);

      another_name = g_string_new (newname);
      g_string_append (another_name, ".-");
      flag = guash_move_image_file (filename, another_name->str);
      g_string_free (another_name, TRUE);

      RETURN flag;
    }
}

static gint
guash_open_image_file (GCHAR *filename)
{
  GimpParam*	return_vals;
  gint		retvals;
  gint		i_id;

  if (! guash_validate_src_file (filename))
    return -1;

  return_vals = gimp_run_procedure ("gimp_file_load",
				    &retvals,
				    GIMP_PDB_INT32, GIMP_RUN_INTERACTIVE,
				    GIMP_PDB_STRING, filename,
				    GIMP_PDB_STRING, filename,
				    GIMP_PDB_END);

  if (return_vals[0].data.d_status != GIMP_PDB_SUCCESS)
    {
#if defined(DEBUG) || defined(DEATH_ATTACK)
#else
      gimp_destroy_params (return_vals, retvals);
#endif
      return -1;
    }
#if defined(DEBUG) || defined(DEATH_ATTACK)
#else
  else
    gimp_destroy_params (return_vals, retvals);
#endif

  i_id = return_vals[1].data.d_image;
  /* the following sequence is copied from app/fileops.c */
  /*  enable & clear all undo steps  */
  gimp_image_undo_enable (i_id);
  /*  set the image to clean  */
  gimp_image_clean_all (i_id);
  /*  display the image */
  gimp_display_new (i_id);
  return i_id;
}

static gint
guash_validate_src_file (GCHAR *filename)
{
  if (os_file_kind (filename, TRUE) == NOT_EXIST)
    {
      GCHAR	tmp[LINE_BUF_SIZE];

      sprintf (tmp, "%s does not exist", filename);
      gtkW_message_dialog (TRUE, tmp);
      return FALSE;
    }
  return TRUE;
}

/*
 * selection class: internal data structure
 */
static gint
selection_add (gint index)
{
  return directory_cache_update_selection (cwd_cache, SELECTION_ADD, index);
}

static gint
selection_add_and_show (gint index)
{
  if (selection_add (index))
    {
      gint	x, y;
      gint	border = THUMBNAIL_BORDER_WIDTH;

      index -= cwd_cache->display_page * the_panel.nthumb_in_page;
      x = INDEX_TO_X (index) - border - 1;
      y = INDEX_TO_Y (index) - border - 1;

      display_request_redraw_rect (x, y,
				   THUMBNAIL_WIDTH + 2 * (border + 1),
				   THUMBNAIL_HEIGHT + 2 * (border + 1));
      return TRUE;
    }
  else
    return FALSE;
}

static gint
selection_delete (gint index)
{
  Thumbnail	*thumb = directory_cache_get_nth (cwd_cache, index);

  if (thumb && HAS_ATTRIBUTE (thumb, SELECTED_P))
    {
      thumbnail_panel_draw_selection_frame (FALSE);
      directory_cache_update_selection (cwd_cache, SELECTION_DELETE, index);

      if (selection_is_active ())
	display_request_redraw ();
      else
	thumbnail_panel_set_info (NULL);
      thumbnail_panel_update_selection_buttons ();
      return TRUE;
    }
  else
    return FALSE;
}

static gint
selection_is_active ()
{
  return (0 < cwd_cache->num_selection);
}

static gint
selection_length ()
{
  return cwd_cache->num_selection;
}

static gint
selection_member_p (gint index)
{
  return HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache, index), SELECTED_P);
}

static gint
selection_reset ()
{
  Thumbnail	*thumb;
  gint		i;
  gint		max = directory_cache_num_entry (cwd_cache);

  G_ASSERT (cwd_cache);

  _DEBUGBLOCK (N_("selection_reset\n"));

  thumbnail_panel_draw_selection_frame (FALSE);
  DPRINT (N_("success to call thumbnail_panel_clear_selection_frame\n"));

  if (0 < cwd_cache->num_selection)
    {
      G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
							cwd_cache->selection_top),
			       SELECTED_P));

      for (i = cwd_cache->selection_top; i < max; i++)
	{
	  thumb = directory_cache_get_nth (cwd_cache, i);

	  G_ASSERT (thumb);

	  if (HAS_ATTRIBUTE (thumb, SELECTED_P))
	    RESET_ATTRIBUTE (thumb, SELECTED_P);
	}
    }
  cwd_cache->num_selection = 0;
  cwd_cache->selection_top = -1;
  cwd_cache->selection_timestamp = 0;
  DPRINT (N_("success to remove all thumbnails from selection\n"));

  thumbnail_panel_update_selection_buttons ();
  /* gtk_widget_queue_draw (thumbnail_panel); */

  cwd_cache->last_focus = 0;
  RETURN TRUE;
}

static gint
selection_reverse_member (gint index)
{
  gint	flag = selection_member_p (index);

  if (flag)
    selection_delete (index);
  else
    selection_add_and_show (index);
  return (! flag);
}

static gint
selection_validate_image ()
{
  if (selection_is_active ())
    return TRUE;
  else
    {
      thumbnail_panel_set_info (_("No image is selected"));
      return FALSE;
    }
}

static selection_iterator *
selection_make_iterator (gint mode)
{
  selection_iterator	*si;
  gint			length = selection_length ();
  gint			index = 0;
  gint			max = directory_cache_num_entry (cwd_cache);
  gint			id;
  gint			count = 0;

  __DEBUGBLOCK (N_("selection_make_iterator (length %d)\n"), length);

  if (length < 1)
    RETURN NULL;

  si = g_new (selection_iterator, 1);
  si->length = length;
  si->unvisit_index = 0;
  si->body = g_new (selection_iterator_entry, length);

  G_ASSERT (selection_is_active ());
  G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
						    cwd_cache->selection_top),
			   SELECTED_P));

  for (id = cwd_cache->selection_top; id < max; id++)
    {
      Thumbnail	*thumb = directory_cache_get_nth (cwd_cache, id);

      G_ASSERT (thumb != NULL);

      if (HAS_ATTRIBUTE (thumb, SELECTED_P)
	  && HAS_NO_ATTRIBUTE (thumb, DELETED_P))
	{
	  selection_iterator_entry *entry = si->body + index++;

	  entry->index = id;
	  entry->selection_timestamp = thumb->selection_timestamp;
	  entry->thumbnail = thumb;
	  count++;
	}
    }
  DPRINT (N_("success to gather selected %d thumbnails\n"), index);

  G_ASSERT (count == cwd_cache->num_selection);

  if (mode == SELECTION_ORDER_TIMESTAMP)
    {
      qsort (si->body,
	     length,
	     sizeof (selection_iterator_entry),
	     (sort_compare_function) selection_iterator_compare_timestamp);
      DPRINT (N_("success to sort by timestamp\n"));
    }
  RETURN si;
}

static gint
selection_iterator_compare_timestamp (void *a, void *b)
{
  gint	a_stamp = ((selection_iterator_entry *) a)->selection_timestamp;
  gint	b_stamp = ((selection_iterator_entry *) b)->selection_timestamp;

  if (a_stamp < b_stamp)
    return -1;
  else if (a_stamp == b_stamp)
    return 0;
  else
    return 1;
}

static selection_iterator_entry *
selection_iterator_get_next_entry (selection_iterator *si)
{
  __DEBUGBLOCK (N_("selection_iterator_get_next_entry\n"));

  if (si == NULL)
    RETURN NULL;

  DPRINT (N_("unvisit_index: %d, length: %d\n"), si->unvisit_index, si->length);

  if (si->unvisit_index < si->length)
    {
      RETURN si->body + si->unvisit_index++;
    }
  else
    {
      DPRINT (N_("delete iterator\n"));
      g_free (si->body);
      g_free (si);
      RETURN NULL;
    }
}

static Thumbnail *
selection_iterator_get_next_thumbnail (selection_iterator *si)
{
  selection_iterator_entry	*next = NULL;

  __DEBUGBLOCK (N_("selection_iterator_get_next_thumbnail\n"));

  next = selection_iterator_get_next_entry (si);
  DPRINT (N_("success to call selection_iterator_get_next_entry (%ld)\n"),
	  (next == NULL ? 0 : (glong) next->thumbnail));

  RETURN (next == NULL ? NULL : next->thumbnail);
}

static void
selection_map_script ()
{
  gint			retvals;
  FILE			*file;
  GCHAR			script_file[PATH_LENGTH];
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			buf[LINE_BUF_SIZE];

  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("selection_map_script\n"));

  if (os_file_kind (guash_tmp_dir, TRUE) != DIRECTORY)
    {
      gtkW_message_dialog (TRUE, "Abort execution: you don't have an appropriate directory");
      printf ("guash_tmp_dir was set to %s: %d\n", guash_tmp_dir, os_file_kind (guash_tmp_dir, TRUE));
      RETURN;
    }

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);

  thumbnail_panel_set_info (_("Map script-fu: wait a moment..."));

  sprintf (script_file, N_("%s%cguash%d.scm"), guash_tmp_dir, G_DIR_SEPARATOR, getpid ());
  DPRINT (N_("script_file is %s\n"), script_file);

  if ((file = fopen (script_file, "w")) == NULL)
    {
      sprintf (buf, _(" Failed to save to %s "), script_file);
      gtkW_message_dialog (TRUE, buf); /* info is too short to display filename */
      RETURN;
    }
  fprintf (file, N_(";; This file was generated by guash automatically.\n"));
  fprintf (file, N_(";; You can delete me without trouble.\n\n"));
  fprintf (file, N_("(set! %%guash-selection\n"));
  fprintf (file, N_("  '(\n"));

  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      guint	ppos;
	 guint i;

      fprintf (file,
	       N_("    (\"%s%c%s\" \"%s\" \""),
	       cwd_cache->name, G_DIR_SEPARATOR, selected->name, /* full path */
	       cwd_cache->name /* directory that does not end with slash */
	       );

      ppos = pathname_get_last_period_index (selected->name);

      for (i = 0; i < ppos; i++)
	fprintf (file, N_("%c"), selected->name[i]);

      fprintf (file, N_("\" \""));

      for (i = ppos + 1; i < strlen (selected->name); i++)
	fprintf (file, N_("%c"), selected->name[i]);

      fprintf (file, N_("\")\n"));
    }
  fprintf (file, N_("    ))\n"));
  fclose (file);

#ifdef GIMP_HAVE_PARASITES
#ifndef BROKEN_SCRIPT_FU
  {
    GimpParasite *para = NULL;

    /*
    if ((para = gimp_find_parasite (GUASH_PARASITE_NAME)) != NULL)
      gimp_detach_parasite (GUASH_PARASITE_NAME);
    */
     gimp_attach_new_parasite (GUASH_PARASITE_NAME,
			       FALSE,
			       strlen (script_file) + 1,
			       script_file);
  }
#endif
#endif

  /* And all script-fus return NULL always */
#ifndef BROKEN_SCRIPT_FU
  thumbnail_panel_set_info (_("Map script-fu: start launchar..."));

  gimp_run_procedure ("script-fu-map-on-guash-selection",
		      &retvals,
		      GIMP_PDB_INT32, GIMP_RUN_INTERACTIVE,
		      GIMP_PDB_STRING, "/tmp/guash", 	/* the filename */
		      GIMP_PDB_INT32, TRUE,		/* delete the file */
		      GIMP_PDB_STRING, "gm-sample",	/* function */
		      GIMP_PDB_INT32, FALSE,		/* display */
		      GIMP_PDB_INT32, FALSE,		/* overwrite */
		      GIMP_PDB_INT32, FALSE,		/* sort */
		      GIMP_PDB_INT32, FALSE,		/* reverse order */
		      GIMP_PDB_STRING, "()",		/* extra arg list */
		      GIMP_PDB_END);

  os_delete_file (script_file);
  thumbnail_panel_set_info (_("Map script-fu: finished"));
#else
  retvals = 0;
  sprintf (buf, _("Wrote %s. Run script-fu-map-on-guash-selection with it"),
	   script_file);
  thumbnail_panel_set_info (buf);
#endif
  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  RETURN;
}

static void
selection_map_unix_command ()
{
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			template[LINE_BUF_SIZE];

  ASSERT (cwd_cache_validate ());

  gtkW_query_box ("Guash: mapping unix command on selection",
		  "Type command to map on the selected image files. \n\
 \"{}\" in the command string is replaced to each filename without directory. \n\
 \"%%\" in the command string is replaced to the directory part of each file. \n\
 - SAMPLES -\n\
 rm .xvpics/{} \n\
 uuencode {} {} > /tmp/{}.uu; chmod go-r /tmp/{}.uu",
		  VAL.mapping_command, template);
  if (strlen (template) == 0)
    return;

  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*command;

      command = guash_generate_unix_command (template, cwd_cache->name, selected->name);
      if (command)
	{
	  system (command);
	  g_free (command);
	}
    }
  strcpy (VAL.mapping_command, template);
}

static void
selection_map_jpeg_thumbnail ()
{
  selection_iterator 	*iterator;
  Thumbnail		*selected;

  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("selection_map_html_thumnail\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*jpeg_thumb;

      jpeg_thumb = pathname_build_jpeg_thumbnail_filename (selected->name);

      if (os_file_kind (jpeg_thumb, TRUE) == NOT_EXIST)
	thumbnail_save_as_jpeg_thumbnail (selected, cwd_cache->name);
    }

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  RETURN;
}

static void
selection_dump_as_html ()
{
  FILE			*file;
  GCHAR			output_file[PATH_LENGTH];
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			buf[LINE_BUF_SIZE];

  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("selection_dump_as_html\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);

  thumbnail_panel_set_info (_("Building jpeg thumbnails..."));

  selection_map_jpeg_thumbnail ();

  thumbnail_panel_set_info (_("Saving index file..."));

  sprintf (output_file, N_("index-guash%d.html"), getpid ());
  DPRINT (N_("output_file is %s\n"), output_file);

  if ((file = fopen (output_file, "w")) == NULL)
    {
      sprintf (buf, _(" Failed to save to %s "), output_file);
      gtkW_message_dialog (TRUE, buf); /* info is too short to display filename */
      RETURN;
    }
  fprintf (file, N_("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n"));
  fprintf (file, N_("<html><head><title>index</title></head>\n\n"));
  fprintf (file, N_("<body>\n"));

  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*jpeg_thumb;

      fprintf (file, N_("<a href=\"%s\"><img src=\""), selected->name);

      jpeg_thumb = pathname_build_jpeg_thumbnail_filename (selected->name);

      fprintf (file, jpeg_thumb);

      g_free (jpeg_thumb);

      fprintf (file, N_("\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"%s\"></a>\n"),
	       selected->image->width, selected->image->height,
	       (selected->info != NULL) ? selected->info : selected->name);

      fprintf (file, N_("<br>"));
    }
  fprintf (file, N_("<p>generated by <a href=\"http://www.gimp.org/~narazaki/\">guash</a>\n"));
  fprintf (file, N_("</body>\n</html>\n"));
  fclose (file);

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  sprintf (buf, "%s is generated successfully", output_file);
  thumbnail_panel_set_info (buf);
  RETURN;
}

static void
selection_dump_as_html_listing ()
{
  FILE			*file;
  GCHAR			output_file[PATH_LENGTH];
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			buf[LINE_BUF_SIZE];

  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("selection_dump_as_html_listing\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);

  thumbnail_panel_set_info (_("Building jpeg thumbnails..."));

  selection_map_jpeg_thumbnail ();

  thumbnail_panel_set_info (_("Saving index file..."));

  sprintf (output_file, N_("index-guash%d.html"), getpid ());
  DPRINT (N_("output_file is %s\n"), output_file);

  if ((file = fopen (output_file, "w")) == NULL)
    {
      sprintf (buf, _(" Failed to save to %s "), output_file);
      gtkW_message_dialog (TRUE, buf); /* info is too short to display filename */
      RETURN;
    }
  fprintf (file, N_("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n"));
  fprintf (file, N_("<html><head><title>index</title></head>\n\n"));
  fprintf (file, N_("<body>\n"));
  fprintf (file, N_("<table align=\"center\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">\n"));

  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*jpeg_thumb;

      fprintf (file, N_("<tr>\n"));
      fprintf (file, N_("  <td width=\"%d\" height=\"%d\" align=\"center\" valign=\"center\">"),
	       THUMBNAIL_WIDTH+10, THUMBNAIL_HEIGHT+10);

      fprintf (file, N_("<a href=\"%s\"><img src=\""), selected->name);

      jpeg_thumb = pathname_build_jpeg_thumbnail_filename (selected->name);

      fprintf (file, jpeg_thumb);

      g_free (jpeg_thumb);

      fprintf (file, N_("\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"%s\"></a>\n"),
	       selected->image->width, selected->image->height,
	       (selected->info != NULL) ? selected->info : selected->name);
      fprintf (file, N_("  </td>\n"));
      fprintf (file, N_("  <td width=\"80%%\">Fill it.\n  </td>\n"));
      fprintf (file, N_("</tr>\n"));
    }
  fprintf (file, N_("</table>\n"));

  fprintf (file, N_("<p>generated by <a href=\"http://www.gimp.org/~narazaki/\">guash</a>\n"));
  fprintf (file, N_("</body>\n</html>\n"));
  fclose (file);

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  sprintf (buf, "%s is generated successfully", output_file);
  thumbnail_panel_set_info (buf);
  RETURN;
}

static void
selection_dump_as_html_guash_table ()
{
  FILE			*file;
  GCHAR			output_file[PATH_LENGTH];
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			buf[LINE_BUF_SIZE];
  gint			index;

  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("selection_dump_as_html_guash_table\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);

  thumbnail_panel_set_info (_("Building jpeg thumbnails..."));

  selection_map_jpeg_thumbnail ();

  thumbnail_panel_set_info (_("Saving index file..."));

  sprintf (output_file, N_("index-guash%d.html"), getpid ());
  DPRINT (N_("output_file is %s\n"), output_file);

  if ((file = fopen (output_file, "w")) == NULL)
    {
      sprintf (buf, _(" Failed to save to %s "), output_file);
      gtkW_message_dialog (TRUE, buf); /* info is too short to display filename */
      RETURN;
    }

  fprintf (file, N_("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n"));
  fprintf (file, N_("<html><head><title>index</title></head>\n\n"));
  fprintf (file, N_("<body>\n"));
  fprintf (file, N_("<table align=\"center\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">\n"));

  index = 0;
  iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*jpeg_thumb;

      if (index % the_panel.ncol == 0)
	{
	  /* start new line */
	  fprintf (file, N_(" <tr>\n"));
	}

      fprintf (file, N_("  <td width=\"%d\" height=\"%d\" align=\"center\" valign=\"bottom\">"),
	       THUMBNAIL_WIDTH+20, THUMBNAIL_HEIGHT+30);

      fprintf (file, N_("   <a href=\"%s\"><img src=\""), selected->name);

      jpeg_thumb = pathname_build_jpeg_thumbnail_filename (selected->name);

      fprintf (file, jpeg_thumb);

      g_free (jpeg_thumb);

      fprintf (file, N_("\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"%s\"></a><br>\n"),
	       selected->image->width, selected->image->height,
	       (selected->info != NULL) ? selected->info : selected->name);

      fprintf (file, N_("   %s"), selected->name);
      fprintf (file, N_("  </td>\n"));

      if (++index % the_panel.ncol == 0)
	{
	  /* terminate the current line */
	  fprintf (file, N_(" </tr>\n"));
	}
    }
  fprintf (file, N_("</table>\n"));

  fprintf (file, N_("<p>generated by <a href=\"http://www.gimp.org/~narazaki/\">guash</a>\n"));
  fprintf (file, N_("</body>\n</html>\n"));
  fclose (file);

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  sprintf (buf, "%s is generated successfully", output_file);
  thumbnail_panel_set_info (buf);
  RETURN;
}

static void
selection_open_files ()
{
  Thumbnail		*selected;
  selection_iterator 	*iterator;
  gint			first_missing = TRUE;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  DEBUGBLOCK (N_("selection_open_files\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);

  thumbnail_panel_set_info (_("Loading..."));

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      gint	f_kind;

      if (HAS_NO_ATTRIBUTE (selected, SELECTED_P))
	continue;

      f_kind = os_file_kind (selected->name, TRUE);

      if (f_kind == REGFILE)
	{
	  GCHAR		filename[PATH_LENGTH];

	  BUILD_ABSOLUTE (filename, selected->name);
	  guash_open_image_file (filename);
	}
      else if (f_kind == NOT_EXIST)
	{
	  if (first_missing)
	    {
	      gdk_beep ();
	      thumbnail_panel_set_info (_("the selected image does not exist now!"));
	      DPRINT (N_("file lost %s\n"), selected->name);
	      sleep (1);
	      first_missing = FALSE;
	    }
	}
    }

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  if (guash_discard_events ())
    thumbnail_panel_set_info (_("FYI, events during opening an image were discarded"));
  else
    {
      if (cwd_cache->last_focus <= 0)
	{
	  /* printf ("cwd_cache->last_focus = %d\n", cwd_cache->last_focus); */
	  thumbnail_panel_set_info (_("FYI, events during opening an image were discarded"));
	}
      else
	{
	  Thumbnail *thumb = NULL;

	  G_ASSERT (0 < cwd_cache->last_focus);
	  thumb = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus);
	  G_ASSERT (thumb != NULL);
	  thumbnail_panel_set_info (thumb->info);
	}
    }

  DEBUGEND;
}

static void
selection_copy_files_to (GCHAR *pathname)
{
  selection_iterator 	*iterator;
  Thumbnail		*selected;
  GCHAR			info[256], first_file[LINE_BUF_SIZE];
  gint			kind = NOT_EXIST;
  gint			success = 0;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  DEBUGBLOCK (N_("selection_copy_files_to (\"%s\")\n"), pathname);

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      kind = os_file_kind (pathname, TRUE);

      if ((kind == NOT_EXIST) || (kind == DIRECTORY))
	{
	  GString	*name, *new;

	  name = g_string_new (cwd_cache->name);
	  g_string_append_c (name, G_DIR_SEPARATOR);
	  G_ASSERT (selected->name);
	  g_string_append (name, selected->name);
	  new = g_string_new (pathname);
	  if (kind == DIRECTORY)
	    {
	      if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
		g_string_append_c (new, G_DIR_SEPARATOR);
	      g_string_append (new, selected->name);
	    }
	  if (os_file_kind (new->str, TRUE) != NOT_EXIST)
	    {
	      sprintf (info, _("%s already exists"), new->str);
	      gtkW_message_dialog (TRUE, info);
	    }
	  else if (guash_copy_image_file (name->str, new->str))
	    {
	      guash_add_entry (new->str, selected);

	      if (success++ == 0)
		strcpy (first_file, selected->name);
	    }

	  g_string_free (name, TRUE);
	  g_string_free (new, TRUE);
	}
      else if (kind == REGFILE)
	{
	  sprintf (info, _("%s already exists"), pathname);
	  gtkW_message_dialog (TRUE, info);
	}
    }

  if (0 < success)
    cwd_cache_update_after_file_operation (success, "copied", first_file, pathname);
  else
    thumbnail_panel_set_info ("Failed to copy");

  RETURN;
}

static void
selection_delete_files ()
{
  selection_iterator 		*iterator;
  selection_iterator_entry 	*entry;
  GCHAR				first_file[LINE_BUF_SIZE];
  gint				success = 0;
  gint				volatilefile = 0;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  DEBUGBLOCK (N_("selection_delete_files\n"));

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (entry = selection_iterator_get_next_entry (iterator)))
    {
      Thumbnail	*selected = entry->thumbnail;
      GCHAR	pathname[PATH_LENGTH];

      BUILD_ABSOLUTE (pathname, selected->name);

      if (os_file_kind (pathname, TRUE) == NOT_EXIST)
	{
	  volatilefile++;
	  if ((success == 0) && (volatilefile == 1))
	    strcpy (first_file, selected->name);
	  selection_delete (entry->index);
	  SET_ATTRIBUTE (selected, DELETED_P);
	}
      else if (os_delete_file (pathname) == 0)
	{
	  GCHAR *thumbnail_name;

	  thumbnail_name = pathname_build_thumbnail_filename (pathname);
	  os_delete_file (thumbnail_name);
	  selection_delete (entry->index);
	  SET_ATTRIBUTE (selected, DELETED_P);
	  if (success++ == 0)
	    strcpy (first_file, selected->name);
	  DPRINT (N_("success to delete %s\n"), selected->name);

	  g_free (thumbnail_name);
	}
    }
  DPRINT (N_("done and display the page %d\n"), cwd_cache->display_page);

  if (0 < success)
    {
      SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
      cwd_cache_update_after_file_operation (success + volatilefile, "deleted",
					     first_file, NULL);
    }
  else
    {
      if (0 < volatilefile)
	{
	  SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
	  cwd_cache_update_after_file_operation (volatilefile, "deleted",
						 first_file, NULL);
	}
      else
	thumbnail_panel_set_info (_("Failed to delete (permission problem?)"));
    }

  DEBUGEND;
}

static void
selection_move_files_to (GCHAR *pathname)
{
  selection_iterator		*iterator;
  selection_iterator_entry 	*entry;
  GCHAR				info[256], first_file[LINE_BUF_SIZE];
  gint				kind = NOT_EXIST;
  gint				success = 0;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  DEBUGBLOCK (N_("selection_move_files_to (\"%s\")\n"), pathname);

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (entry = selection_iterator_get_next_entry (iterator)))
    {
      Thumbnail *selected = entry->thumbnail;

      G_ASSERT (selected != NULL);

      kind = os_file_kind (pathname, TRUE); /* might be overwrited */
      if ((kind == NOT_EXIST) || (kind == DIRECTORY))
	{
	  GCHAR src_name[PATH_LENGTH];
	  GCHAR dest_name[PATH_LENGTH];

	  BUILD_ABSOLUTE (src_name, selected->name);

	  if (kind == DIRECTORY)
	    {
	      if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
		sprintf (dest_name, "%s%c%s", pathname, G_DIR_SEPARATOR, selected->name);
	      else
		sprintf (dest_name, "%s%s", pathname, selected->name);
	    }
	  else
	    sprintf (dest_name, "%s", pathname);

	  DPRINT (N_("src : %s, dest: %s\n"), src_name, dest_name);

	  if (guash_move_image_file (src_name, dest_name))
	    {
	      guash_add_entry (dest_name, selected);
	      selection_delete (entry->index);
	      SET_ATTRIBUTE (selected, DELETED_P);
	      SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
	      if (success++ == 0)
		strcpy (first_file, selected->name);
	    }
	}
      else if (kind == REGFILE)
	{
	  sprintf (info, _("%s already exists"), pathname);
	  gtkW_message_dialog (TRUE, info);
	}
    }
  DPRINT (N_("success to move file(s)\n"));

  if (0 < success)
    cwd_cache_update_after_file_operation (success, "moved", first_file, pathname);
  else
    thumbnail_panel_set_info (_("Failed to move (permission problem?)"));

  RETURN;
}

/*
 * thumbnail_panel: visible element
 */

static void
thumbnail_panel_initialize_banner ()
{
  guchar	data[3] = BASE_COLOR;
  guchar	*ptr;

  DEBUGBLOCK (N_("thumbnail_panel_initialize_banner\n"));
#ifndef GIMP_VERSION
  banner_data=g_malloc(banner_width * banner_height);
#endif
  if (VAL.last_dir_name[0] != 0)
    {
      banner_width = the_panel.width;
      banner_height = 2;	/* for BASE_COLOR and FRAME_COLOR */
    }

  /* to call thumbnail_panel_clear, only the 1st line should be cleared. */
  banner = image_buffer_new (banner_width, banner_height, 3);
  for (ptr = banner->data; ptr < banner->data + 3 * banner->width;)
    {
      *ptr++ = *data;
      *ptr++ = *(data + 1);
      *ptr++ = *(data + 2);
    }

  thumbnail_panel_clear ();
  if (VAL.last_dir_name[0] == 0)
    image_buffer_set_from_header (banner,
				  banner_data,
				  banner_width, banner_height);
  RETURN;
}

static void
thumbnail_panel_reinitialize_banner ()
{
  guchar	base[3] = BASE_COLOR;
  guchar	frame[3] = FRAME_COLOR;
  guchar	*ptr;

  DEBUGBLOCK (N_("thumbnail_panel_reinitialize_banner\n"));

  /* Then, we use banner to clear thumbnail_panel. Thus clear the 1st row. */
  for (ptr = banner->data; ptr < banner->data + 3 * banner->width;)
    {
      *ptr++ = *base;
      *ptr++ = *(base + 1);
      *ptr++ = *(base + 2);
    }
  /* And use second row to draw frame */
  for (ptr = banner->data + 3 * banner->width; ptr < banner->data + 6 * banner->width;)
    {
      *ptr++ = *frame;
      *ptr++ = *(frame + 1);
      *ptr++ = *(frame + 2);
    }

  RETURN;
}

/* Show Wilber without his eye */
static void
thumbnail_panel_show_banner_step1 ()
{
  guchar	white[3] = BASE_COLOR;
  gint		x, y;
  gint		sx, sy;

  DEBUGBLOCK (N_("thumbnail_panel_show_banner_step1\n"));

  if (VAL.last_dir_name[0] != 0)
    {
      RETURN;
    }

  sx = MAX (0, ((the_panel.width - banner->width) / 2));
  sy = MAX (0, ((the_panel.height - banner->height) / 2));

  thumbnail_panel_draw_image_buffer (banner, sx, sy, FALSE);

  for (y = 19; y < 24; y++)
    for (x = 101; x < 106; x++)
      thumbnail_panel_draw_rgb (sx + x, sy + y, 1, 1, white);

  {
    gint	i;
    guchar	color[3];
    guchar	draw[3] = DRAW_COLOR;

    for (i = 0; i < 3; i++)
      color[i] = (2 * white[i] + draw[i]) / 3;

    thumbnail_panel_draw_string (GUASH_VERSION,
				 the_panel.width / 2, sy + banner->height,
				 color, TRUE, the_panel.width);

    gtkW_preview_force_to_update (thumbnail_panel);
  }

  RETURN;
}

/* Show Wilber with eye */
static void
thumbnail_panel_show_banner_step2 ()
{
  guchar	color[3];
  guchar	draw[3] = DRAW_COLOR;
  guchar	white[3] = BASE_COLOR;
  gint		sx, sy,  y;
  gint		i;

  DEBUGBLOCK (N_("thumbnail_panel_banner_step2\n"));

  sx = MAX (0, ((the_panel.width - banner->width) / 2));
  sy = MAX (0, ((the_panel.height - banner->height) / 2));

  thumbnail_panel_clear ();
  thumbnail_panel_draw_image_buffer (banner, sx, sy, FALSE);

  for (y = 18; y < 24; y++)
    thumbnail_panel_draw_rgb (sx + 100, sy + y, 6, 1,
			      banner->data + ((y * banner->width + 100) * 3));

  for (i = 0; i < 3; i++)
    color[i] = (white[i] + draw[i]) / 2;

  thumbnail_panel_draw_string (GUASH_VERSION,
			       the_panel.width / 2, sy + banner->height,
			       color, TRUE, the_panel.width);

  gtkW_preview_force_to_update (thumbnail_panel);

  RETURN;
}

static void
thumbnail_panel_show_banner_step3 ()
{
  guchar	draw[3] = DRAW_COLOR;
  guchar	white[3] = BASE_COLOR;
  gint		sx, sy, x, y;

  DEBUGBLOCK (N_("thumbnail_panel_show_banner_step3\n"));

  sx = MAX (0, ((the_panel.width - banner->width) / 2));
  sy = MAX (0, ((the_panel.height - banner->height) / 2));

  thumbnail_panel_draw_string (GUASH_VERSION,
			       the_panel.width / 2, sy + banner->height,
			       draw, TRUE, the_panel.width);

  gtkW_preview_force_to_update (thumbnail_panel);

  for (y = 18; y < 24; y++)
    for (x = 101; x < 106; x++)
      thumbnail_panel_draw_rgb (sx + x, sy + y, 1, 1, white); 

  for (y = 19; y < 24; y++)
    thumbnail_panel_draw_rgb (sx + 103, sy + y + 2, 6, 1,
			      banner->data + ((y * banner->width + 100) * 3));

  gtkW_preview_force_to_update (thumbnail_panel);

  RETURN;
}

static void
thumbnail_panel_clear_rectangle (gint ex, gint ey, gint ew, gint eh)
{
  image_buffer *bg = NULL;
  guchar	base_color[3] = BASE_COLOR; 

  if (! thumbnail_panel)
    return;

  __DEBUGBLOCK (N_("thumbnail_panel_clear_rectangle(%d, %d, %d, %d)\n"),
		ex, ey, ew, eh);

  if (!(bg = the_panel.bg)
      || (bg->width != the_panel.width)
      || (bg->height != the_panel.height))
    {
      gint x, y;
      guchar *ptr;

      if (bg)
	image_buffer_resize (bg, the_panel.width, the_panel.height, 3);
      else
	the_panel.bg = image_buffer_new (the_panel.width, the_panel.height, 3);

      bg = the_panel.bg;
      ptr = bg->data;

      for (y = 0; y < the_panel.height; y++)
	for (x = 0; x < the_panel.width; x++)
	  {
	    /*
	    *ptr++ = *banner->data;
	    *ptr++ = *(banner->data + 1);
	    *ptr++ = *(banner->data + 2);
	    */
	    *ptr++ = base_color[0];
	    *ptr++ = base_color[1];
	    *ptr++ = base_color[2];
	  }
    }
  thumbnail_panel_draw_rgb (ex, ey, ew, eh, bg->data);

  DEBUGEND;
}

static void
thumbnail_panel_clear ()
{
  DEBUGBLOCK (N_("thumbnail_panel_clear()\n"));

  thumbnail_panel_clear_rectangle (0, 0, the_panel.width, the_panel.height);

  DEBUGEND;
}

static void
thumbnail_panel_draw_rgb (gint x, gint y, gint w, gint h, guchar *buffer)
{
  if (! the_panel.gc)
    {
      the_panel.gc = gdk_gc_new (thumbnail_panel->window);
      gdk_gc_set_exposures (the_panel.gc, TRUE);
    }

  gdk_draw_rgb_image (thumbnail_panel->window,
		      the_panel.gc,
		      x,
		      y,
		      w,
		      h,
		      GDK_RGB_DITHER_MAX,
		      buffer,
		      w * 3);
}

static void
thumbnail_panel_draw_image_buffer (image_buffer *buffer,
				   gint sx,
				   gint sy,
				   gint align_p)
{
  gint	width, height;
  gint	y;

  G_ASSERT (buffer);

  DEBUGBLOCK (N_("thumbnail_panel_draw_image_buffer (width: %d, height %d)\n"),
	      buffer->width, buffer->height);

  if (buffer->data == NULL)
    RETURN;

  if (align_p)
    {
      sx += (THUMBNAIL_WIDTH - buffer->width) / 2;
      sy += (THUMBNAIL_HEIGHT - buffer->height) / 2;
    }

  width = MIN (the_panel.width - sx, buffer->width);
  height = MIN (the_panel.height - sy, buffer->height);

  if (buffer->ch == 1)
    {
      guchar	fixed_buf[THUMBNAIL_WIDTH * 3];
      guchar	*dynamic_buffer = NULL;
      guchar	*buf = NULL;
      guchar	*data = buffer->data;

      DPRINT (N_("draw indexed image\n"));

      if (buffer->width <= THUMBNAIL_WIDTH)
	buf = fixed_buf;
      else
	buf = dynamic_buffer = g_new (guchar, buffer->width * 3);

      for (y = 0; y < height; y++)
	{
	  gint		x;
	  guchar	*tmp = buf;

	  for (x = 0; x < buffer->width; x++)
	    tmp = indexed_to_rgb (*data++, tmp);

	  thumbnail_panel_draw_rgb (sx, sy + y, width, 1, buf);
	}
      if (dynamic_buffer)
	g_free (dynamic_buffer);
    }
  else
    {
      thumbnail_panel_draw_rgb (sx, sy, width, height, buffer->data);
    }
  RETURN;
}

static void
thumbnail_panel_draw_string (GCHAR *string,
			     gint x,
			     gint y,
			     guchar *color,
			     gint align_p,
			     gint frame_width)
{
  GdkFont	*font = the_panel.font;
  GdkImage	*image = NULL;
#ifdef DRAW_STRING_BY_CHARACTER
  GCHAR		buffer[LINE_BUF_SIZE];
#endif
  guchar	base[3] = BASE_COLOR;
  guchar	*ptr = NULL;
  gint		ox, oy, width = 0, height;

  DEBUGBLOCK (N_("thumbnail_panel_draw_string (\"%s\",...)\n"), string);

  if ((string == NULL) || (string[0] == 0))
    RETURN;

#ifdef DRAW_STRING_BY_CHARACTER
  {
    gint	index = strlen (string);
    gint	rest;
    gint	font_width = the_panel.font_height * 2; /* should be safe */

    strcpy (buffer, string);
    while (0 < (rest = (width = gdk_string_width (font, buffer)) - frame_width))
      {
	index -= MAX (rest / font_width, 1);
	buffer[index] = 0;
      }
    string = buffer;
  }
#else
  width = gdk_string_width (font, string);
  width = MIN (width, frame_width);
#endif

  if (align_p)
    x -= width / 2;

  height = the_panel.font_height;
  if (the_panel.height <= y + height)
    {
      DPRINT (N_("abort:too tall\n"));
      RETURN;
    }

  if (! text_buffer || (text_buffer->width < width) || (text_buffer->height < height))
    {
      if (text_buffer)
	{
	  gdk_pixmap_unref (text_buffer->pixmap);
	  gdk_gc_destroy (text_buffer->gc);
	  g_free (text_buffer->line_buffer);
	  g_free (text_buffer);
	}
      text_buffer = g_new (Pixmap_struct, 1);
      text_buffer->width = width;
      text_buffer->height = height;
      text_buffer->pixmap = gdk_pixmap_new (NULL, width, height, 1);
      text_buffer->line_buffer = g_malloc (3 * width * height);
      text_buffer->gc = gdk_gc_new (text_buffer->pixmap);
      gdk_gc_set_font (text_buffer->gc, font);
#if !defined (GDK_WINDOWING_WIN32)
      text_buffer->black.pixel
	= BlackPixel (((Display *) GDK_DISPLAY()),
		      DefaultScreen (((Display *) GDK_DISPLAY())));
#else
      text_buffer->black.pixel = 0;
      text_buffer->black.red = 
      text_buffer->black.green = 
      text_buffer->black.blue = 0;
#endif
#if !defined (GDK_WINDOWING_WIN32) 
      text_buffer->white.pixel
	= WhitePixel (((Display *) GDK_DISPLAY()),
		      DefaultScreen (((Display *) GDK_DISPLAY())));
#else
      /* //HB: ??? */
      text_buffer->white.pixel = 1;
      text_buffer->white.red = 
      text_buffer->white.green = 
      text_buffer->white.blue = 65535;
#endif

      DPRINT (N_("success to build text_buffer\n"));
    }
  gdk_gc_set_foreground (text_buffer->gc, &text_buffer->white);
  /* assure the bg of the region is in white */
  gdk_draw_rectangle (text_buffer->pixmap, text_buffer->gc, 1, 0, 0,
		      text_buffer->width, text_buffer->height);
  gdk_gc_set_foreground (text_buffer->gc, &text_buffer->black);

  gdk_draw_string (text_buffer->pixmap, font, text_buffer->gc,
		   0, font->ascent, string);

  DPRINT (N_("success to draw \"%s\" on text_buffer\n"), string);

  /* then make GdkImage */
  if (! (image = gdk_image_get (text_buffer->pixmap, 0, 0, width, height)))
    {
      DPRINT (N_("abort: fail to gdk_image_get\n"));
      RETURN;
    }

  for (oy = 0; oy < height; oy++)
    {
      ptr = text_buffer->line_buffer + 3 * width * oy;
      for (ox = 0; ox < width; ox++)
	{
	  if (gdk_image_get_pixel (image, ox, oy) == text_buffer->black.pixel)
	    {
	      *ptr++ = *color;
	      *ptr++ = *(color + 1);
	      *ptr++ = *(color + 2);
	    }
	  else
	    {
	      *ptr++ = *base;
	      *ptr++ = *(base + 1);
	      *ptr++ = *(base + 2);
	    }
	}
      /* copy it to preview's buffer */
    }
  thumbnail_panel_draw_rgb (x, y, width, height, text_buffer->line_buffer);

  DPRINT (N_("success to gtk_preview_draw_row\n"));

  gdk_image_destroy (image);

  RETURN;
}

static gint
thumbnail_panel_draw_thumbnail (Thumbnail *thumb)
{
  guchar	draw[3] = DRAW_COLOR;
  guchar	slink[3] = SLINK_COLOR;
  guchar	*color = NULL;
  gint		sx, sy;
  gint		pdir_p;
  image_buffer	*icon = NULL;

  G_ASSERT (thumb);

  __DEBUGBLOCK (N_("thumbnail_panel_draw_thumbnail of %s\n"), thumb->name);

  the_panel.last_index++;
  if (the_panel.nthumb_in_page <= the_panel.last_index)
    {
      RETURN FALSE;
    }

  sx = INDEX_TO_X (the_panel.last_index);
  sy = INDEX_TO_Y (the_panel.last_index);

  pdir_p = HAS_ATTRIBUTE (thumb, PARENT_DIRECTORY_P);
  color = HAS_ATTRIBUTE (thumb, SYMLINK_P) ? slink : draw;

  if (HAS_ATTRIBUTE (thumb, DIRECTORY_P))
    icon = pdir_p ? pdir_icon : dir_icon;
  else
    {
      if (thumb->image &&
	  (0 < thumb->image->width) && (0 < thumb->image->height))
	icon = thumb->image;
      else
	icon = file_icon;
    }

  thumbnail_panel_draw_image_buffer (icon, sx, sy, TRUE);
#ifndef GDK_USE_UTF8_MBS
  thumbnail_panel_draw_string ((pdir_p ? "..(parent)" : thumb->name),
			       sx + THUMBNAIL_WIDTH / 2,
			       sy + THUMBNAIL_HEIGHT + THUMBNAIL_TSEPARATOR,
			       color,
			       TRUE,
			       THUMBNAIL_WIDTH);
#else
  {
    gchar* tmp = g_filename_to_utf8 (thumb->name, NULL);
    thumbnail_panel_draw_string ((pdir_p ? "..(parent)" : tmp),
                                 sx + THUMBNAIL_WIDTH / 2,
                                 sy + THUMBNAIL_HEIGHT + THUMBNAIL_TSEPARATOR,
                                 color,
                                 TRUE,
                                 THUMBNAIL_WIDTH);
    g_free(tmp);
  }
#endif
  DPRINT (N_("success to draw_string\n"));

  RETURN TRUE;
}

static void
thumbnail_panel_set_directory_info ()
{
  GCHAR	title[LINE_BUF_SIZE];
  GCHAR label[LINE_BUF_SIZE];
  gint	from = 0;

  sprintf (title, "%s: %s", SHORT_NAME, pathname_get_basename (cwd_cache->name));
#ifndef GDK_USE_UTF8_MBS
  gtk_window_set_title (GTK_WINDOW (dlg), title);
#else
  {
    gchar* tmp = g_filename_to_utf8 (title, NULL);
    gtk_window_set_title (GTK_WINDOW (dlg), title);
    g_free(tmp);
  }
#endif

  the_panel.current_page1 = cwd_cache->display_page + 1;

  if (1 < cwd_cache_npage ())
    sprintf (label,
	     "%s%s (%d/%d by %s) ",
	     (from == 0) ? "" : "*",
	     cwd_cache->name + from,
	     cwd_cache->display_page + 1,
	     cwd_cache_npage (),
	     (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P) ? "name" : "date"));
  else
    sprintf (label,
	     "%s%s (by %s)",
	     (from == 0) ? "" : "*",
	     cwd_cache->name + from,
	     (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P) ? "name" : "date"));
#ifndef GDK_USE_UTF8_MBS
  gtk_label_set_text (GTK_LABEL (cwd_label), label);
#else
  {
    gchar* tmp = g_filename_to_utf8 (label, NULL);
    gtk_label_set_text (GTK_LABEL (cwd_label), tmp);
    g_free(tmp);
  }
#endif
  gtk_label_set_justify (GTK_LABEL (cwd_label), GTK_JUSTIFY_FILL);
}

static void
thumbnail_panel_set_info (GCHAR *name)
{
  G_ASSERT (GTK_IS_LABEL (file_property));
  _DEBUGBLOCK ("thumbnail_panel_set_info (\"%s\")\n", name);
  gtk_label_set_text (GTK_LABEL (file_property),
		      ((name == NULL) ? the_panel.info : name));
  /* Since info field should be updated immediately, I use gtk_widget_draw ()
     instead of gtk_widget_queue_draw () here. */
  DPRINT ("update file_property\n");
  gtk_widget_realize (file_property);
  gtk_widget_show (file_property);

  gtk_widget_queue_draw (file_property);
  gtk_widget_draw (file_property, NULL);

  DPRINT ("event handling...\n");
  during_buildup_thumbnail_panel = TRUE;
  while (gtk_events_pending())
    gtk_main_iteration();
  during_buildup_thumbnail_panel = FALSE;
  DPRINT ("event handling...done\n");
  display_request_set_handler ();
  RETURN;
}

static void
thumbnail_panel_set_info_default ()
{
  if (! cwd_cache)
    the_panel.info[0] = 0;
  else if (1 == cwd_cache->ndir)
    sprintf (the_panel.info, "%d %s",
	     cwd_cache->nimage,
	     (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
	      ? ((1 < cwd_cache->nimage) ? _("images") : _("image"))
	      : ((1 < cwd_cache->nimage) ? _("files") : _("file"))));
  else
    sprintf (the_panel.info, _("%d %s and %d %s"),
	     cwd_cache->nimage,
	     (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
	      ? ((1 < cwd_cache->nimage) ? _("images") : _("image"))
	      : ((1 < cwd_cache->nimage) ? _("files") : _("file"))),
	     (cwd_cache->ndir - 1),
	     ((2 < cwd_cache->ndir) ? _("subdirectories") : _("subdirectory")));
}

static void
thumbnail_panel_draw_frame (gint index, gint draw_p)
{
  gint		x, y;
  gint		xi, yi;
  gint		endx;
  gint		scan = 0;
  gint		border = THUMBNAIL_BORDER_WIDTH;
  guchar 	*data;

  index -= cwd_cache->display_page * the_panel.nthumb_in_page;
  x = INDEX_TO_X (index);
  y = INDEX_TO_Y (index);

  data = banner->data + (draw_p ? 3 * banner->width : 0);

  /* draw top */
  endx = x + THUMBNAIL_WIDTH + border + 1;
  for (yi = y - border - 1;
       yi < y - 1;
       yi++)
    for (xi = x - border - 1;
	 xi < endx;
	 xi += scan)
      {
	scan = MIN (endx - xi, banner->width);
	thumbnail_panel_draw_rgb (xi, yi, scan, 1, data);
      }
  /* draw left */
  endx = x - 1;
  for (yi = y - border - 1;
       yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
       yi++)
    for (xi = x - border - 1;
	 xi < endx;
	 xi += scan)
      {
	scan = MIN (endx - xi, banner->width);
	thumbnail_panel_draw_rgb (xi, yi, scan, 1, data);
      }
  /* draw right */
  endx = x + THUMBNAIL_WIDTH + border + 1;
  for (yi = y - border - 1;
       yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
       yi++)
    for (xi = x + THUMBNAIL_WIDTH + 1;
	 xi < endx;
	 xi += scan)
      {
	scan = MIN (endx - xi, banner->width);
	thumbnail_panel_draw_rgb (xi, yi, scan, 1, data);
      }
  /* draw bottom */
  for (yi = y + THUMBNAIL_FULLHEIGHT + 1;
       yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
       yi++)
    for (xi = x - border - 1;
	 xi < endx;
	 xi += scan)
      {
	scan = MIN (endx - xi, banner->width);
	thumbnail_panel_draw_rgb (xi, yi, scan, 1, data);
      }
}

static void
thumbnail_panel_draw_selection_frame (gint mode)
{
  gint	max, index;
  gint	flag = FALSE;

  max = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page,
	     directory_cache_num_entry (cwd_cache));

  for (index = cwd_cache->display_page * the_panel.nthumb_in_page;
       index < max;
       index++)
    if (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache, index), SELECTED_P))
      {
	thumbnail_panel_draw_frame (index, mode);
	flag = TRUE;
      }
  if (flag)
    gtk_widget_queue_draw (thumbnail_panel);
}

static void
thumbnail_panel_finalize_update ()
{
  __DEBUGBLOCK (N_("thumbnail_panel_finalize_update\n"));

  if (! cwd_cache)
    RETURN;

  the_panel.current_page1 = cwd_cache->display_page + 1;
  thumbnail_panel_set_directory_info ();
  thumbnail_panel_set_info_default ();
  if (selection_is_active ())
    {
      Thumbnail *selected;

      selected = directory_cache_get_nth (cwd_cache,
					  cwd_cache->selection_top);
      thumbnail_panel_draw_selection_frame (TRUE);
      thumbnail_panel_set_info (selected->info);
    }
  else
    thumbnail_panel_set_info (NULL);
  thumbnail_panel_update_selection_buttons ();
  thumbnail_panel_update_sensitive_menu ();

  RETURN;
}

static void
thumbnail_panel_size_request (GtkWidget      *widget,
			      GtkRequisition *requisition)
{
  gint	ncol, nrow;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_DRAWING_AREA (widget));
  g_return_if_fail (requisition != NULL);

  __DEBUGBLOCK (N_("thumbnail_panel_size_request\n"));

  if (! cwd_cache)
    {
      ncol = the_panel.ncol;
      nrow = the_panel.nrow;
    }
  else
    {
      ncol = NCOL_OF_THUMBNAIL_PANEL_MIN;
      nrow = NROW_OF_THUMBNAIL_PANEL_MIN;
    }
  requisition->width = COL2WIDTH (ncol);
  requisition->height = ROW2HEIGHT (nrow);

  RETURN;
}

static gint
thumbnail_panel_size_allocate (GtkWidget *widget, gpointer value)
{
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_DIALOG (widget), FALSE);

  __DEBUGBLOCK (N_("thumbnail_panel_size_allocate\n"));

  /* widget->allocation = *allocation;*/
  widget = thumbnail_panel;

  if ((the_panel.width != widget->allocation.width)
      || (the_panel.height != widget->allocation.height))
    the_panel.resized = TRUE;

  the_panel.width = widget->allocation.width;
  the_panel.height = widget->allocation.height;

  the_panel.ncol = WIDTH2COL (the_panel.width);
  the_panel.nrow = HEIGHT2ROW (the_panel.height);
  the_panel.nthumb_in_page = the_panel.ncol * the_panel.nrow;

  if (GTK_WIDGET_REALIZED (widget))
    {
      if (cwd_cache)
	if (((GtkAdjustment *) widget_pointer[0].widget)->upper != cwd_cache_npage () + 1)
	  {
	    the_panel.current_page1 = cwd_cache_npage () + 1;
	    ((GtkAdjustment *) widget_pointer[0].widget)->upper
	      = cwd_cache_npage () + 1;
	  }
    }
  
  gtk_drawing_area_size (GTK_DRAWING_AREA (thumbnail_panel),
			 the_panel.width,
			 the_panel.height);

  RETURN FALSE;
}

/* this function does not check the current size of thumbnail_panel */
static gint
thumbnail_panel_update_rectangle (gint ex, gint ey, gint ew, gint eh)
{
  gint	index, max;
  gint	x, y;

  __DEBUGBLOCK (N_("thumbnail_panel_update_rectangle (%d, %d, %d, %d\n"),
		ex, ey, ew, eh);

  if (! cwd_cache)
    RETURN FALSE;
  if ((ew == 0) && (eh == 0))
    RETURN FALSE;

  if (! RUBBER_BAND_P)
    {
      gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
      gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
    }
  /* from 0.99.5, this function must also check the page number, since resize
     changes the number of pages. */
  DPRINT (N_("display_page: %d, max_page: %d\n"),
	  cwd_cache->display_page,
	  (cwd_cache_npage () - 1));
  cwd_cache->display_page = CLAMP (cwd_cache->display_page,
				   0,
				   (cwd_cache_npage () - 1));
  the_panel.current_page1 = cwd_cache->display_page + 1;

  thumbnail_panel_clear_rectangle (ex, ey, ew, eh);

  max = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page,
	     directory_cache_num_entry (cwd_cache));

  the_panel.last_index = -1;
  for (index = cwd_cache->display_page * the_panel.nthumb_in_page;
       index < max;
       index++)
    {
      x = INDEX_TO_X (the_panel.last_index + 1);
      y = INDEX_TO_Y (the_panel.last_index + 1);
      if ((ex <= x + THUMBNAIL_WIDTH) && (x< ex + ew)
	  && (ey <= y + THUMBNAIL_FULLHEIGHT) && (y < ey + eh))
	{
	  Thumbnail *thumb = directory_cache_get_nth (cwd_cache, index);

	  thumbnail_panel_draw_thumbnail (thumb);
	  if (HAS_ATTRIBUTE (thumb, SELECTED_P))
	    thumbnail_panel_draw_frame (index, TRUE);
	}
      else
	the_panel.last_index++;
    }
  if (RUBBER_BAND_P)
    RETURN TRUE;

  display_request_finalize ();

  DPRINT (N_("success to call thumbnail_panel_finalize_update\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

  display_request_set_handler ();

  RETURN TRUE;
}

static gint
thumbnail_panel_update ()
{
  DEBUGBLOCK (N_("thumbnail_panel_update\n"));

  thumbnail_panel_update_rectangle (0, 0, the_panel.width, the_panel.height);

  RETURN TRUE;
}

static void
thumbnail_panel_update_rubber_band (gint x, gint y)
{
  gint small_x, small_y, big_x, big_y;

  if (! RUBBER_BAND_P)
    return;

  small_x = MIN (the_panel.start_x, the_panel.end_x);
  small_y = MIN (the_panel.start_y, the_panel.end_y);
  big_x = MAX (the_panel.start_x, the_panel.end_x);
  big_y = MAX (the_panel.start_y, the_panel.end_y);

  /* erase old lines */
  thumbnail_panel_update_rectangle (small_x,
				    small_y,
				    big_x - small_x + 1,
				    big_y - small_y + 1);
  if ((small_x != big_x)
      || (small_y != big_y)
      || (POS_TO_INDEX (the_panel.end_x, the_panel.end_y) != POS_TO_INDEX (x, y)))
    {
      the_panel.end_x = x;
      the_panel.end_y = y;
      small_x = MIN (the_panel.start_x, the_panel.end_x);
      small_y = MIN (the_panel.start_y, the_panel.end_y);
      big_x = MAX (the_panel.start_x, the_panel.end_x);
      big_y = MAX (the_panel.start_y, the_panel.end_y);

      gdk_draw_rectangle (thumbnail_panel->window, the_panel.gc, 0,
			  small_x, small_y,
			  big_x - small_x, big_y - small_y);
    }
  else
    {
      if ((0 <= small_x) && (0 <= big_x)
	  && (0 <= small_y) && (0 <= big_y))
      the_panel.end_x = x;
      the_panel.end_y = y;

      display_request_redraw_rect (small_x, small_y,
				   big_x - small_x,
				   big_y - small_y);
    }
}

static void
thumbnail_panel_finalize_rubber_band (gint to_selection)
{
  gint index;
  gboolean flag = FALSE;
  gint small_x, small_y, big_x, big_y;

  if (! to_selection)
    {
      the_panel.in_motion_p = FALSE;
      RESET_RUBBER_AREA;
      return;
    }

  small_x = MIN (the_panel.start_x, the_panel.end_x);
  small_y = MIN (the_panel.start_y, the_panel.end_y);
  big_x = MAX (the_panel.start_x, the_panel.end_x);
  big_y = MAX (the_panel.start_y, the_panel.end_y);
  the_panel.in_motion_p = FALSE;
  RESET_RUBBER_AREA;

  if ((small_x < big_x) || (small_y < big_y))
    display_request_redraw_rect (small_x, small_y,
				 big_x - small_x,
				 big_y - small_y);

  if (POS_TO_INDEX (small_x, small_y) != POS_TO_INDEX (big_x, big_y))
    {
      for (index = POS_TO_INDEX (0, 0);
	   index < POS_TO_INDEX (0, 0) + the_panel.nthumb_in_page;
	       index++)
	{
	  gint thumb_x = INDEX_TO_X (index);
	  gint thumb_y = INDEX_TO_Y (index);

	  if (directory_cache_valid_image_index (cwd_cache, index)
	      && (small_x < thumb_x + THUMBNAIL_WIDTH)
	      && (small_y < thumb_y + THUMBNAIL_FULLHEIGHT)
	      && (thumb_x < big_x)
	      && (thumb_y < big_y))
	    {
	      selection_add (index);
	      flag = TRUE;
	    }
	  {
	    display_request_redraw ();
	    display_request_set_handler ();
	  }
	}
    }
}

static gint
thumbnail_panel_update_delayed (GtkWidget *widget)
{
  if (0 < delayed_updating)
    {
      delayed_updating = 0;
      thumbnail_panel_update ();
    }
 /* By returning FALSE, this idle_func is removed automatically. */
  return FALSE;
}

/* this function does not check the current size of thumbnail_panel */
static gint
thumbnail_panel_update_partially (gint index)
{
  gint	shown_first = cwd_cache->display_page * the_panel.nthumb_in_page;
  gint	unshown_first = shown_first + the_panel.nthumb_in_page;

  DEBUGBLOCK (N_("thumbnail_panel_update_partially\n"));

  if ((index < shown_first) || (unshown_first <= index))
    RETURN FALSE;

  if (index == shown_first)
    {
      the_panel.last_index = -1;
      thumbnail_panel_clear ();
    }
  thumbnail_panel_draw_thumbnail (directory_cache_get_nth (cwd_cache, index));
  DPRINT (N_("success to thumbnail_panel_draw_thumbnail\n"));

  RETURN (index == unshown_first - 1);
}

static void
thumbnail_panel_update_sensitive_menu ()
{
  DEBUGBLOCK (N_("thumbnail_panel_update_sensitive_menu\n"));

  thumbnail_panel_update_selection_buttons ();
  thumbnail_panel_update_scroller ();

  RETURN;
}

static void
thumbnail_panel_update_selection_buttons ()
{
  gint	ncommand =
    sizeof (image_selection_commands) / sizeof (image_selection_commands[0]);
  gint	flag = selection_is_active ();
  gint	i;

  for (i = 0; i < ncommand; i++)
    if (image_selection_commands[i].sensitive & SENSITIVE_SELECTION)
      gtk_widget_set_sensitive (image_selection_commands[i].widget, flag);
  for (i = 0 ; i < NSELECTION_BUTTON; i++)
    if (widget_for_selecion[i] != NULL)
      gtk_widget_set_sensitive (widget_for_selecion[i], flag);
}

#define GO_BACKWARD 0
#define GO_FORWARD 1
static void
thumbnail_panel_update_scroller ()
{
  gint		ncommand;
  gint		i;
  gint		flags[2];
  GtkAdjustment *adjustment;
  image_command_table *table;

  DEBUGBLOCK (N_("thumbnail_panel_update_scroller display_page %d, max %d\n"),
	      cwd_cache->display_page, cwd_cache_npage ());

  if (! cwd_cache)
    RETURN;
  if ((widget_pointer[0].widget == NULL) || (widget_pointer[0].updater == NULL))
    RETURN;

  flags[GO_FORWARD] = flags[GO_BACKWARD] = TRUE; /* 0 for back(prev), 1 for next */
  if (cwd_cache->display_page == 0)
    flags[GO_BACKWARD] = FALSE;
  if (cwd_cache_npage () <= 1 + cwd_cache->display_page)
    flags[GO_FORWARD] = FALSE;

  {
    GCHAR buffer[GTKW_ENTRY_BUFFER_SIZE];

    sprintf (buffer, "%d", the_panel.current_page1);
    gtk_entry_set_text (GTK_ENTRY (widget_for_scroll[1]), buffer);
  }

  adjustment = (GtkAdjustment *) widget_pointer[0].widget;
  adjustment->value = the_panel.current_page1;

  if (adjustment->upper != cwd_cache_npage () + 1)
    {
      gdouble	current_page = the_panel.current_page1;

      DPRINT (N_("change upper from %d to %d\n"),
	      (gint) adjustment->upper,
	      cwd_cache_npage () + 1);

      adjustment->upper	= cwd_cache_npage () + 1;
      /* force update! Wed Aug 26 11:01:02 1998 */
      adjustment->value = -1;
      /* From 0.99.5 changes of the_panel.current_page1 propagates here. */
      gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");

      DPRINT (N_("adjustment->value :%d, old :%d\n"),
	      (gint) adjustment->value,
	      (gint) current_page);
      /* as a side-effect, value is reseted. [Mon Sep 14 15:05:45 1998] */
      adjustment->value = current_page;
      the_panel.current_page1 = current_page;
      /* dirty design! to stop the 2nd panel flush, cancel the result of
	 signal_emit [Wed Sep 16 19:15:02 1998] */
      _DPRINT (N_("delayed updating %d\n"), delayed_updating);
      delayed_updating = 0;
    }
  /* The following statement is still required to update scroll-bar. */
  (widget_pointer[0].updater) (widget_pointer);

  for (i = 0; i < NSCROLLER; i++)
    {
      gtk_widget_set_sensitive (widget_for_scroll[i],
				(1 < cwd_cache_npage ()));
      gtk_widget_queue_draw (widget_for_scroll[i]);
    }

  ncommand = sizeof (image_root_commands) / sizeof (image_root_commands[0]);
  table = image_root_commands;

  for (i = 0; i < ncommand; i++)
    {
      if (table[i].sensitive & SENSITIVE_NOT_LAST_PAGE)
	gtk_widget_set_sensitive (table[i].widget, flags[GO_FORWARD]);
      if (table[i].sensitive & SENSITIVE_NOT_FIRST_PAGE)
	gtk_widget_set_sensitive (table[i].widget, flags[GO_BACKWARD]);
    }

  DPRINT (N_("display_page: %d\n"), cwd_cache->display_page);

  RETURN;
}
#undef GO_BACKWARD
#undef GO_FORWARD

static void
thumbnail_panel_create_menu_for (GtkWidget *menu,
				 image_command_table *commands,
				 gint ncommand,
				 GtkAccelGroup *accel_group)
{
  GtkWidget	*menu_item;
  gint	i;

  gtk_menu_set_accel_group (GTK_MENU (menu), accel_group);
  for (i = 0; i < ncommand; i++)
    {
      if (commands[i].label)
	menu_item = gtk_menu_item_new_with_label (_(commands[i].label));
      else
	menu_item = gtk_menu_item_new ();

      commands[i].widget = menu_item;
      gtk_menu_append (GTK_MENU (menu), menu_item);
      if (commands[i].command)
	gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
			    (GtkSignalFunc) commands[i].command,
			    (gpointer) commands[i].label);
      else if (commands[i].submenu)
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
				   *(commands[i].submenu));

      if (commands[i].label
	  && (commands[i].binding[binding_style].key != 0))
	gtk_widget_add_accelerator (menu_item,
				    "activate",
				    accel_group,
				    commands[i].binding[binding_style].key,
				    commands[i].binding[binding_style].mod,
				    GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
      gtk_widget_show (menu_item);
      if (! commands[i].label)	/* separator should not focus on */
	gtk_widget_set_sensitive (menu_item, FALSE);
    }
}

static GtkWidget *
_menu_create (image_command_table *table, gint size, GtkAccelGroup *agroup)
{
  GtkWidget *menu;

  menu = gtk_menu_new ();
  thumbnail_panel_create_menu_for (menu, table, size, agroup);

  return menu;
}

#define SIZE(a)		(sizeof (a) / sizeof (a[0]))

static void
thumbnail_panel_create_menu ()
{
  GtkAccelGroup		*accel_group = gtk_accel_group_new ();
  gint	modified = FALSE;

  DEBUGBLOCK (N_("thumbail_panel_create_menu\n"));
  if (! thumbnail_panel_directory_menu)
    {
      thumbnail_panel_directory_menu
	= _menu_create (image_directory_commands,
			SIZE (image_directory_commands),
			accel_group);
      modified = TRUE;
    }
  if (! thumbnail_panel_preference_menu)
    {
      thumbnail_panel_preference_menu
	= _menu_create (preference_commandns,
			SIZE (preference_commandns),
			accel_group);
      modified = TRUE;
    }
  if (! thumbnail_panel_root_menu)
    {
      thumbnail_panel_root_menu
	= _menu_create (image_root_commands,
			SIZE (image_root_commands),
			accel_group);
      modified = TRUE;
    }
  if (! thumbnail_panel_selection_map_menu)
    {
      thumbnail_panel_selection_map_menu
	= _menu_create (image_selection_map_commands,
			SIZE (image_selection_map_commands),
			accel_group);
      modified = TRUE;
    }
  if (! thumbnail_panel_selection_menu)
    {
      thumbnail_panel_selection_menu
	= _menu_create (image_selection_commands,
			SIZE (image_selection_commands),
			accel_group);
      modified = TRUE;
    }
  if (! thumbnail_panel_hidden_menu)
    {
      thumbnail_panel_hidden_menu
	= _menu_create (image_hidden_commands,
			SIZE (image_hidden_commands),
			accel_group);
      modified = TRUE;
    }
  if (modified)
    gtk_window_add_accel_group (GTK_WINDOW (dlg), accel_group);
  RETURN;
}
#undef SIZE

static gint
thumbnail_panel_move_focus (gint offset)
{
  gint	index = -1;
  gint	new_index = -1;
  gint		new_page = cwd_cache->display_page;
  Thumbnail	*thumbnail;

  DEBUGBLOCK (N_("thumbnail_panel_move_focus (%d)\n"), offset);

  if (selection_is_active ())
    {
      index = cwd_cache->last_focus;
      if (! directory_cache_valid_image_index (cwd_cache, index))
	index = cwd_cache->selection_top;
      new_index = index + offset;
    }
  else
    if (0 < cwd_cache->nimage)
      {
	if (0 < offset)
	  new_index = MAX ((cwd_cache->display_page * the_panel.nthumb_in_page),
			   cwd_cache->ndir);
	else
	  new_index = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page -1,
			   (directory_cache_num_entry (cwd_cache) -1));
      }
  if (directory_cache_valid_image_index (cwd_cache, new_index))
    {
      if (0 < index)
	selection_delete (index);
      new_page = new_index / (the_panel.ncol * the_panel.nrow);
      if (new_page != cwd_cache->display_page)
	{
	  cwd_cache->display_page = new_page;
	  display_request_redraw ();
	}
      selection_add_and_show (new_index);
      thumbnail = directory_cache_get_nth (cwd_cache, new_index);
      thumbnail_panel_set_info (thumbnail->info);
      cwd_cache->last_focus = new_index;
    }
  else
    thumbnail_panel_set_info (NULL);

  thumbnail_panel_update_sensitive_menu ();

  RETURN new_index;
}

/*
 * directory_cache: internal data structure
 */
static void
directory_cache_initialize (directory_cache *dcache)
{
  INITIALIZE_ATTRIBUTES (dcache);

  dcache->savable = FALSE;
  dcache->ndir = 0;
  dcache->nimage = 0;
  dcache->display_page = 0;
  dcache->selection_timestamp = 0;
  dcache->num_selection = 0;
  dcache->selection_top = -1;
  dcache->last_focus = 0;
  dcache->timestamp = 0;
}

static directory_cache *
directory_cache_new (GCHAR *name)
{
  directory_cache 	*p;
  gint			i;

  DEBUGBLOCK (N_("directory_cache_new\n"));

  p = g_new (directory_cache, 1);
  strcpy (p->name, name);
  p->birth_index = directory_cache_table_size;

  p->max_ndir = 2;
  p->dir = g_new (Thumbnail, p->max_ndir);
  memset (p->dir, 0, p->max_ndir * sizeof (Thumbnail));
  for (i = 0; i < p->max_ndir; i++)
    thumbnail_initialize (p->dir + i);

  p->max_nimage = 16;
  p->image = g_new (Thumbnail, p->max_nimage);
  memset (p->image, 0, p->max_nimage * sizeof (Thumbnail));
  for (i = 0; i < p->max_nimage; i++)
    thumbnail_initialize (p->image + i);

  directory_cache_initialize (p);
  p->last_focus = 0;

  RETURN p;
}

static void
directory_cache_auto_shrink (directory_cache *dcache, gint force)
{
  gint	true_last;
  gint	i;
  gint	nfree = 0;

  __DEBUGBLOCK (N_("directory_cache_auto_shrink (%d in %d)\n"),
		dcache->nimage, dcache->max_nimage);

  true_last = dcache->max_nimage - 1;
  while ((0 <= true_last) && ((dcache->image + true_last)->image == NULL))
    true_last--;
  /* now true_last is the index of image which has alloccated memory. */

  if (true_last < 0)
    RETURN;

  if (force)
    nfree = true_last - dcache->nimage + 1;
  else if (dcache->nimage * 2 < true_last)
    nfree = MAX ((true_last - dcache->nimage) / 2, 1);

  /* release memory in reverse order */
  DPRINTIF (0 < nfree) (N_("free %d thumbnail->images\n"), nfree);

  for (i = true_last; true_last - nfree < i; i--)
    thumbnail_free_image (dcache->image + i);

  /*
  {
    gint flag = TRUE;

    DPRINT (N_("seek last index...\n"));

    true_last = dcache->max_nimage - 1;
    while ((0 <= true_last) && flag)
      {
	if ((dcache->image + true_last)->image != NULL)
	  flag = FALSE;
	else
	  true_last--;
      }
    DPRINT (N_("new allocated last is %d\n"), true_last);
  }
  */
  RETURN;
}

static gint
directory_cache_garbage_collect (directory_cache *dcache)
{
  /* side-effect: selection is reset and thumbnail_panel is updated
   */
  gint		index;
  gint		old_ndir, old_nimage;
  gint		nsel = 0;
  Thumbnail	*chunk;

  _DEBUGBLOCK (N_("directory_cache_garbage_collect\n"));

  old_ndir = dcache->ndir;
  chunk = dcache->dir;
  index = 0;
  while (index < dcache->ndir)
    {
      if (HAS_ATTRIBUTE (chunk + index, DELETED_P))
	{
	  GCHAR		*name, *info;

	  /* shift to the left */
	  dcache->ndir--;
	  info = chunk[index].info;
	  name = chunk[index].name;

	  g_memmove (chunk + index,
		     chunk + (index + 1),
		     sizeof (Thumbnail) * (dcache->ndir - index));
	  /* the dubbling pointer is back to the world */
	  chunk[dcache->ndir].info = info;
	  chunk[dcache->ndir].name = name;
	}
      else
	index++;
    }

  old_nimage = dcache->nimage;
  chunk = dcache->image;
  index = 0;
  while (index < dcache->nimage)
    {
      if (HAS_ATTRIBUTE (chunk + index, DELETED_P))
	{
	  image_buffer	*ibuf;
	  GCHAR		*name, *info;

	  G_ASSERT (HAS_NO_ATTRIBUTE (chunk + index, SELECTED_P));

	  /* shift to the left */
	  dcache->nimage--;
	  ibuf = chunk[index].image;
	  info = chunk[index].info;
	  name = chunk[index].name;

	  g_memmove (chunk + index,
		     chunk + (index + 1),
		     sizeof (Thumbnail) * (dcache->nimage - index));
	  /* the dubbling pointer is back to the world */
	  chunk[dcache->nimage].image = ibuf;
	  chunk[dcache->nimage].info = info;
	  chunk[dcache->nimage].name = name;
	}
      else
	{
	  if (HAS_ATTRIBUTE (chunk + index, SELECTED_P))
	    {
	      nsel++;
	      if (index < dcache->selection_top)
		dcache->selection_top = index + dcache->ndir;
	    }
	index++;
	}
    }

  G_ASSERT (nsel == dcache->num_selection);

  if (dcache == cwd_cache)
    {
      if (cwd_cache_npage () <= dcache->display_page)
	dcache->display_page = cwd_cache_npage () - 1;

      guash_discard_events ();
      DPRINT (N_("npage: %d, display_page: %d\n"),
	      cwd_cache_npage (),
	      dcache->display_page);
      if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
	{
	  directory_cache_reorder (cwd_cache);
	  RESET_ATTRIBUTE (cwd_cache, DISORDERED_P);
	  DPRINT (N_("success to reorder\n"));
	}
      display_request_redraw ();
    }

  RETURN TRUE;
}

static Thumbnail *
directory_cache_get_image (directory_cache *dcache, GCHAR *pathname)
{
  Thumbnail	*val = NULL;

  G_ASSERT (dcache != NULL);
  G_ASSERT (*pathname == G_DIR_SEPARATOR);

  DEBUGBLOCK (N_("directory_cache_get_image (\"%s\", \"%s\")\n"),
	      dcache->name, pathname);

  if (HAS_ATTRIBUTE (dcache, FILED_P)) /* check thumbnail */
    val = guash_get_image_from_file (pathname, TRUE);
  DPRINT (N_("success to load the thumbnail\n"));

  if ((val == NULL)
      && ! (DURING_DRAG_MOTION_P)
      && HAS_ATTRIBUTE (dcache, DISPLAY_IMAGE_P))
    {
      val = guash_get_image_from_file (pathname, FALSE);
      DPRINTIF (val) (N_("success to load the image file\n"));
      if (val != NULL)
	/* Now we can save image as an XV thumbnail.*/
	if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P))
	  {
	    if (dcache->savable == UNKNOWN)
	      directory_cache_make_thumbnail_directory (dcache);
	    if (dcache->savable)
	      {
		directory_cache_save_thumbnail_to (cwd_cache, val);
		DPRINT (N_("success to save thumbnail\n"));
	      }
	  }
    }
  RETURN val;
}

static gint
directory_cache_add_directory (directory_cache *dcache,
			       GCHAR *pathname,
			       gint f_kind)
{
  Thumbnail	*thumbnail;

  DEBUGBLOCK (N_("directory_cache_add_directory (\"%s\", %d)\n"),
	      pathname, f_kind);

  if ((0 < directory_cache_max_images)
      && (directory_cache_max_images < directory_cache_num_entry (dcache)))
    RETURN FALSE;

  if (dcache->max_ndir <= dcache->ndir)
    {
      gint	i = dcache->max_ndir;

      dcache->max_ndir *= 2;
      dcache->dir =
	(Thumbnail *) g_realloc ((gpointer) dcache->dir,
				 dcache->max_ndir * sizeof (Thumbnail));

      for (; i < dcache->max_ndir; i++)
	thumbnail_initialize (dcache->dir + i);

      DPRINT (N_("expand directory table to %d\n"), dcache->max_ndir);
    }

  thumbnail = &dcache->dir[dcache->ndir];

  INITIALIZE_ATTRIBUTES (thumbnail);
  RESET_ATTRIBUTE (thumbnail, DELETED_P);
  SET_ATTRIBUTE (thumbnail, DIRECTORY_P);
  SET_TO_ATTRIBUTE (thumbnail, SYMLINK_P, (f_kind == SLNKDIR));

  if (thumbnail->name)
    g_free (thumbnail->name);
  thumbnail->name = g_strdup (pathname);

  if (dcache->ndir == 0)
    SET_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P);

  if (thumbnail->info)
    g_free (thumbnail->info);
  thumbnail->info = NULL;

  thumbnail_free_image (thumbnail);

  dcache->ndir++;	/* increment before calling update_partially */

  if (dcache == cwd_cache)
    thumbnail_panel_update_partially (dcache->ndir - 1);

  _DPRINT (N_("success to add_directory\n"));

  RETURN TRUE;
}

static gint
directory_cache_add_image (directory_cache *dcache,
			   GCHAR *pathname,
			   Thumbnail *thumb,
			   gint f_kind)
{
  Thumbnail	*thumbnail;

  DEBUGBLOCK (N_("directory_cache_add_image (\"%s\", %s, %d)\n"),
	      pathname, ((thumb != NULL) ? "a thumb." : ""), f_kind);

  if ((0 < directory_cache_max_images)
      && (directory_cache_max_images < directory_cache_num_entry (dcache)))
    {
      DPRINT (N_("done (too many: %d)\n"), dcache->nimage);
      RETURN FALSE;
    }

  if (dcache->max_nimage <= dcache->nimage)
    {
      gint	i = dcache->max_nimage;

      DPRINT (N_("expand image table...\n"));

      dcache->max_nimage *= 2;
      dcache->image
	= (Thumbnail *) g_realloc ((gpointer) dcache->image,
				   dcache->max_nimage * sizeof (Thumbnail));
      for (; i < dcache->max_nimage; i++)
	thumbnail_initialize (dcache->image + i);

      DPRINT (N_("expand image table...done\n"));
    }

  thumbnail = &dcache->image[dcache->nimage];

  G_ASSERT (thumbnail);

  INITIALIZE_ATTRIBUTES (thumbnail);

  if (thumbnail->info)
    g_free (thumbnail->info);

  if (thumb != NULL)
    {
      thumbnail_copy_data (thumbnail, thumb, FALSE);

      if (f_kind == SLNKFILE)
	{
	  GCHAR tmp[LINE_BUF_SIZE];

	  sprintf (tmp, _("(symlink) %s"), thumb->info);
	  thumbnail->info = g_strdup (tmp);
	}
      else
	thumbnail->info = g_strdup (thumb->info);
    }
  else
    {
      thumbnail_free_image (thumbnail);
      thumbnail->info = g_strdup (pathname_get_basename (pathname));
    }

  RESET_ATTRIBUTE (thumbnail, DIRECTORY_P);
  RESET_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P);
  SET_TO_ATTRIBUTE (thumbnail, SYMLINK_P, (f_kind == SLNKFILE));
  if (thumbnail->name)
    g_free (thumbnail->name);
  thumbnail->name = g_strdup (pathname_get_basename (pathname));

  dcache->nimage++;

  if (dcache == cwd_cache)
    thumbnail_panel_update_partially (directory_cache_num_entry (dcache) - 1);

  RETURN TRUE;
}

static gint
directory_cache_num_entry (directory_cache *dcache)
{
  return dcache->ndir + dcache->nimage;
}

static gint
directory_cache_valid_index (directory_cache *dcache, gint index)
{
  return ((0 <= index) && (index < dcache->ndir + dcache->nimage));
}

static gint
directory_cache_valid_subdirectory_index (directory_cache *dcache, gint index)
{
  return ((1 <= index) && (index < dcache->ndir));
}

static gint
directory_cache_valid_image_index (directory_cache *dcache, gint index)
{
  return ((dcache->ndir <= index) && (index < dcache->ndir + dcache->nimage));
}

static directory_cache *
directory_cache_of (GCHAR *name)
{
  return (directory_cache *) g_hash_table_lookup (directory_cache_table,
						  (gpointer) name);
}

static void
directory_cache_reorder (directory_cache *dcache)
{
  int	(func)(Thumbnail *, Thumbnail *);

  DEBUGBLOCK (N_("directory_cache_reorder\n"));

  if (HAS_ATTRIBUTE (dcache, SORT_BY_NAME_P))
    {
      qsort (dcache->dir,
	     dcache->ndir,
	     sizeof (Thumbnail),
	     (sort_compare_function) thumbnail_compare_name);
      qsort (dcache->image,
	     dcache->nimage,
	     sizeof (Thumbnail),
	     (sort_compare_function) thumbnail_compare_name);
      DPRINT (N_("success to reorder by name\n"));
    }

  if (0 < dcache->num_selection)
    {
      gint	i;
      Thumbnail *thumb = dcache->image;

      dcache->selection_top = -1;
      for (i = 0; i < dcache->nimage; i++, thumb++)
	if (HAS_ATTRIBUTE (thumb, SELECTED_P))
	  {
	    dcache->selection_top = i + dcache->ndir;
	    break;
	  }
      DPRINT (N_("success to recompute selection_top\n"));
    }
  RETURN;
}

static void
directory_cache_table_aging (gpointer key,
			     gpointer value,
			     gpointer user_data)
{
  directory_cache *dcache = (directory_cache *) value;

  if (dcache != cwd_cache)
    {
      if (cwd_cache->birth_index < dcache->birth_index)
	dcache->birth_index--;
    }
  DPRINT (N_("birth_index:%d (cwd_cache:%d)\n"),
	  dcache->birth_index,
	  cwd_cache->birth_index);
}

static void
directory_cache_table_purge_subdirectory (gpointer key,
					  gpointer value,
					  gpointer user_data)
{
  directory_cache *dcache = (directory_cache *) value;
  GCHAR	*dir;
  guint	dir_len;

  dir = (GCHAR *)user_data;
  dir_len = strlen (dir);

  if (dir_len <= strlen (dcache->name) &&
      (! strncmp (dir, dcache->name, dir_len)))
    {
      SET_ATTRIBUTE (dcache, PURGED_P);
    }
}

static void
directory_cache_table_recycler (gpointer key,
				gpointer value,
				gpointer user_data)
{
  directory_cache *dcache, **fail_safe;

  dcache = (directory_cache *) value;
  fail_safe = (directory_cache **)user_data;

  /* This is for fail safe */
  if ((cwd_cache == NULL) && (*fail_safe == NULL))
    *fail_safe = dcache;

  /* set the oldest entry to cwd_cache */
  if ((--(dcache->birth_index) == 0) && (cwd_cache == NULL))
    cwd_cache = (directory_cache *) value;
}

static Thumbnail *
directory_cache_get_nth (directory_cache *dcache, gint index)
{
  G_ASSERT (dcache);
  G_ASSERT (index < directory_cache_num_entry (dcache));

  if (index < dcache->ndir)
    {
      G_ASSERT (dcache->dir);
      return dcache->dir + index;
    }
  else
    {
      G_ASSERT (dcache->image);
      return dcache->image + (index - dcache->ndir);
    }
}

static Thumbnail *
directory_cache_lookup (directory_cache *dcache, GCHAR *name)
{
  gint		index;
  Thumbnail	*ptr = dcache->image;

  for (index = 0; index < dcache->nimage; index++)
    if (strcmp ((ptr + index)->name, name) == 0)
      return (ptr + index);
  return NULL;
}

static gint
directory_cache_make_thumbnail_directory (directory_cache *dentry)
{
  GCHAR		*thumbnail_dir;
  mode_t	mode = NEW_DIRECTORY_MODE;

  DEBUGBLOCK (N_("directory_cache_make_thumbnail_directory (\"%s\")\n"),
	      dentry->name);

  thumbnail_dir = pathname_build_thumbnail_dirname (dentry->name);

  if (os_make_directory (thumbnail_dir, mode) == -1)
    {
      switch (errno)
	{
	case EEXIST:
	  dentry->savable = os_file_is_writable (thumbnail_dir);
	  SET_ATTRIBUTE (dentry, FILED_P);
	  break;
	default:
	  dentry->savable = FALSE;
	  RESET_ATTRIBUTE (dentry, FILED_P);
	  break;
	}
      g_free (thumbnail_dir);
      RETURN FALSE;
    }
  else
    {
      dentry->savable = TRUE;
      SET_ATTRIBUTE (dentry, FILED_P);

      g_free (thumbnail_dir);
      RETURN TRUE;
    }
}

static gint
directory_cache_save_thumbnail_to (directory_cache *dcache,
				   Thumbnail *thumb)
{
  GString	*s;

  DEBUGBLOCK (N_("directory_cache_save_thumbnail_to (%s, %s)\n"),
	      dcache->name, thumb->name);

  G_ASSERT (cwd_cache->savable != FALSE);

  s= g_string_new (pathname_build_thumbnail_dirname (dcache->name));
  g_string_append_c (s, G_DIR_SEPARATOR);
  G_ASSERT (thumb->name);
  g_string_append (s, thumb->name);

  dcache->savable = save_thumbnail_as_xvpict_image (s->str, thumb);
  SET_ATTRIBUTE (cwd_cache, FILED_P);

  g_string_free (s, TRUE);

  RETURN TRUE;
}

/* if force is TRUE, all thumbnails and directory are deleted.
 * Otherwise, only obsolete thumbnails are deleted.
 * This function returns TRUE if some thumbnail is old. Otherwise FALSE.
 */
static gint
directory_cache_delete_invalid_cache_files (directory_cache *dcache,
					    gint force)
{
  GCHAR		*path;
  DIR		*dir;
  struct dirent *dentry;
  gint		index = 0;
  gint		flag = FALSE;

  DEBUGBLOCK (N_("directory_cache_delete_invalid_cache_files\n"));

  path = pathname_build_thumbnail_dirname (dcache->name);

  if (os_file_kind (path, TRUE) != DIRECTORY)
    RETURN flag;

  dir = opendir (path);
  rewinddir (dir);

  for (; (dentry = readdir (dir)); index++)
    {
      GString	*file;

      file = g_string_new (g_strdup (path));
      g_string_append_c (file, G_DIR_SEPARATOR);
      g_string_append (file, dentry->d_name);

      if (os_file_kind (file->str, TRUE) == REGFILE)
	{
	  if (force || (! pathname_is_valid_thumbnail_filename (file->str)))
	    {
	      DPRINT (N_("delete thumbnail file: %s\n"), file->str);
	      os_delete_file (file->str);
	      flag = TRUE;
	    }
	}
      g_string_free (file, TRUE);
    }
  if (force)
    {
      if (os_delete_file (path) == 0)
	thumbnail_panel_set_info (_("Thumbnail directory was deleted"));
      else
	thumbnail_panel_set_info (_("Failed to delete thumbnail directory (permission problem?)"));

      RESET_ATTRIBUTE (dcache, FILED_P);
    }

  g_free (path);
  closedir (dir);

  RETURN flag;
}

static GtkWidget *
directory_cache_create_parents_menu ()
{
  GtkWidget	*menu;
  guint		count, index;
  GCHAR		*cwd;
  GtkWidget	*menuitem;
  GCHAR		*label = NULL;

  menu = gtk_menu_new ();
  cwd = cwd_cache->name;

  label = g_new (GCHAR, 2);
  label[0] = G_DIR_SEPARATOR;
  label[1] = 0;
  menuitem = gtk_menu_item_new_with_label (label);
  gtk_menu_append (GTK_MENU (menu), menuitem);
  gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
		      (GtkSignalFunc) menu_change_directory_callback,
		      (gpointer) label);
  gtk_widget_show (menuitem);

  for (count = index = 1; index < strlen (cwd); index++)
    if (cwd[index] == G_DIR_SEPARATOR)
      {
	label = g_new (GCHAR, index + 1);
	g_memmove (label, cwd, index);
	label[index] = 0;

	menuitem = gtk_menu_item_new_with_label (label);
	gtk_menu_prepend (GTK_MENU (menu), menuitem);
	gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
			    (GtkSignalFunc) menu_change_directory_callback,
			  (gpointer) label);
	gtk_widget_show (menuitem);
    }

  return menu;
}

static void
directory_cache_create_history_menu_foreach (gpointer key,
					     gpointer value,
					     gpointer user_data)
{
  GtkWidget	*menu_item;
  GtkWidget	*menu;

  menu = (GtkWidget *) user_data;

  menu_item = gtk_menu_item_new_with_label ((GCHAR *)key);
  gtk_menu_append (GTK_MENU (menu), menu_item);
  gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
		      (GtkSignalFunc) menu_change_directory_callback,
		      key);
  gtk_widget_show (menu_item);
}

static GtkWidget *
directory_cache_create_history_menu ()
{
  GtkWidget *menu;

  menu = gtk_menu_new ();

  g_hash_table_foreach (directory_cache_table,
			directory_cache_create_history_menu_foreach,
			menu);

  return menu;
}

static gint
directory_cache_update_selection (directory_cache *dcache, gint kind, gint index)
{
  Thumbnail	*thumb;

  if (kind == SELECTION_DISORDER)
    {
      gint i;
      gint max = directory_cache_num_entry (dcache);

      /* SELECTION_DISORDER keeps the number of selected thumbnail.
	 Only the position was broken. */
      dcache->selection_top = -1;
      dcache->num_selection = 0;

	for (i = 0; i < max; i++)
	  {
	    if (HAS_ATTRIBUTE (directory_cache_get_nth (dcache, i), SELECTED_P))
	      {
		dcache->num_selection++;
		if (dcache->selection_top == -1)
		  dcache->selection_top = i;
	      }
	  }
	RETURN FALSE;
    }

  thumb = directory_cache_get_nth (dcache, index);

  G_ASSERT (thumb);
  G_ASSERT (0 <= index);

  dcache->last_focus = index;

  if (kind == SELECTION_ADD)
    {
      if (HAS_ATTRIBUTE (thumb, SELECTED_P))
	return TRUE;

      SET_ATTRIBUTE (thumb, SELECTED_P);
      thumb->selection_timestamp = ++dcache->selection_timestamp;

      if (dcache->num_selection == 0)
	dcache->selection_top = index;
      else
	dcache->selection_top = MIN (dcache->selection_top, index);

      dcache->num_selection++;
      DPRINT (N_("num_selection: %d\n"), dcache->num_selection);
      return TRUE;
    }
  else if (kind == SELECTION_DELETE)
    {
      gint	i;
      gint	max = directory_cache_num_entry (dcache);

      if (HAS_NO_ATTRIBUTE (thumb, SELECTED_P))
	return (0 < dcache->num_selection);

      RESET_ATTRIBUTE (thumb, SELECTED_P);

      if (0 < dcache->num_selection)
	dcache->num_selection--;
      DPRINT (N_("num_selection: %d\n"), dcache->num_selection);

      if (dcache->num_selection == 0)
	{
	  dcache->last_focus = 0;
	  return FALSE;
	}
      if (dcache->selection_top == index)
	for (i = index; i < max; i++)
	  {
	    if (HAS_ATTRIBUTE (directory_cache_get_nth (dcache, i), SELECTED_P))
	      {
		dcache->selection_top = i;
		return TRUE;
	      }
	  }
      return TRUE;
    }
  return FALSE;
}

/* name is a short name, which does not include the directory part */
static gint
directory_cache_update_thumbnail_for (directory_cache *dcache,
				      GCHAR *pathname,
				      Thumbnail *src_thumb)
{
  Thumbnail	*thumbnail, *thumb = NULL;
  gint		offset, index;
  gint		last_index;
  GCHAR		*basename = pathname_get_basename (pathname);

  DEBUGBLOCK (N_("directory_cache_update_thumbnail_for (\"%s\", \"%s\" with%s thumb.)\n"),
	      dcache->name,
	      pathname,
	      (src_thumb == NULL ? "out" : ""));

  if (src_thumb)
    thumb = src_thumb;

  if ((thumbnail = directory_cache_lookup (dcache, basename)) == NULL)
    {
      DPRINT (N_("%s is a new image file\n"), basename);

      if ((thumb == NULL)
	  && ((thumb = directory_cache_get_image (dcache, pathname)) == NULL))
	{
	  if (HAS_ATTRIBUTE (dcache, DISPLAY_IMAGE_P))
	    RETURN FALSE;
	}

      if (os_file_kind (pathname, TRUE) == DIRECTORY)
	directory_cache_add_directory (dcache, pathname_get_basename(pathname),
				       os_file_kind (pathname, FALSE));
      else
	directory_cache_add_image (dcache, pathname, thumb,
				   os_file_kind (pathname, FALSE));
      SET_ATTRIBUTE (dcache, DISORDERED_P);
      RETURN TRUE;
    }

  if ((thumb == NULL)
      && ((thumb = directory_cache_get_image (dcache, pathname)) == NULL))
    RETURN FALSE;

  /* copy all data [Sun Sep 13 15:15:24 1998] */
  thumbnail_copy_data (thumbnail, thumb, TRUE);

  DPRINT (N_("success to copy all data\n"));

  if (dcache != cwd_cache)
    RETURN FALSE;

  offset = POS_TO_INDEX (0, 0);
  last_index = MIN (offset + the_panel.nthumb_in_page,
		    directory_cache_num_entry (dcache));
  for (index = offset; index < last_index; index++)
    {
      if (directory_cache_get_nth (dcache, index) == thumbnail)
	{
	  DPRINT (N_("updated thumbnail is the %dth thumb in the panel\n"),
		  index);
	  RETURN TRUE;
	  break;
	}
    }

  RETURN FALSE;
}

/*
 * cwd_cache: special function only for cwd_cache
 */

static gint
cwd_cache_npage ()
{
  gint	page;

  page = directory_cache_num_entry (cwd_cache) / the_panel.nthumb_in_page;
  if (directory_cache_num_entry (cwd_cache) % the_panel.nthumb_in_page != 0)
    page++;
  return page;
}

static gint
cwd_cache_jump_to_subdirectory_index (gint nth)
{
  if (nth < cwd_cache->ndir)
    {
      Thumbnail	*thumb = directory_cache_get_nth (cwd_cache, nth);
      GCHAR	tmp[PATH_LENGTH];

      BUILD_ABSOLUTE (tmp, thumb->name);

      guash_change_current_directory (tmp, thumb);
      return TRUE;
    }
  else
    return FALSE;
}

static gint
cwd_cache_validate ()
{
  __DEBUGBLOCK (N_("cwd_cache_validate\n"));

  if (os_file_kind (cwd_cache->name, TRUE) == NOT_EXIST)
    {
      GCHAR	*new_dir;

      gdk_beep ();
      thumbnail_panel_set_info (_("Error: the directory does not exist now!"));

      DPRINT (N_("cwd_cache->name %s\n"), cwd_cache->name);
      sleep (1);
      new_dir = pathname_get_vaild_directoryname (cwd_cache->name);
      os_file_change_current_directory (new_dir);
      /* to delete the obsolete directory_cache, we must recycle it now.
	 Thus hack the name before calling guash_update_cwd_cache. */
      strcpy (cwd_cache->name, new_dir);
      g_free (new_dir);
      guash_update_cwd_cache (RESCAN);

      thumbnail_panel_set_info (_("Error: the directory does not exist now! Jumped up"));

      RETURN FALSE;
    }
  else
    RETURN TRUE;
}

static gint
cwd_cache_update ()
{
  /* side-effect: selection is reset  */
  GCHAR		path[PATH_LENGTH], counter[256], *fname;
  gint		index_to_update;
  gint		index = 0;
  GCHAR		**filename_list;
  gint		nentry;
  gint		update_step;
  gint		update_threshold;
#ifdef DELETE_CORE
  gint		no_core;
#endif
  gint		dummy_image = -1;

  _DEBUGBLOCK (N_("cwd_cache_update\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  {
    GCHAR	*thumbnail_dir;

    thumbnail_dir = pathname_build_thumbnail_dirname (cwd_cache->name);
    switch (os_file_kind (thumbnail_dir, TRUE))
      {
      case NOT_EXIST:
	cwd_cache->savable = UNKNOWN;
	RESET_ATTRIBUTE (cwd_cache, FILED_P);
	break;
      case DIRECTORY:
	cwd_cache->savable = os_file_is_writable (thumbnail_dir);
	SET_ATTRIBUTE (cwd_cache, FILED_P);
	break;
      default:
	cwd_cache->savable = FALSE;
	RESET_ATTRIBUTE (cwd_cache, FILED_P);
	break;
      }
    g_free (thumbnail_dir);
  }
  /* set attributes here. The values may be used by
     thumbnail_panel_update_partially. */
  COPY_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P, &VAL);
  COPY_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P, &VAL);

  if (HAS_ATTRIBUTE (cwd_cache, FILED_P))
    gtk_window_set_title (GTK_WINDOW (dlg),
			  "Guash: scanning with saved thumbnails...");
  else
    gtk_window_set_title (GTK_WINDOW (dlg),
			  "Guash: wait a minute...");
  gtk_label_set_text (GTK_LABEL (cwd_label), cwd_cache->name);
  gtk_widget_draw (cwd_label, NULL);

  selection_reset ();

  cwd_cache->nimage = cwd_cache->ndir = 0;
  index_to_update = (1 + cwd_cache->display_page) * the_panel.nthumb_in_page;

  {
    GCHAR	*tmp;

    tmp = pathname_get_directoryname (cwd_cache->name);
    directory_cache_add_directory (cwd_cache, tmp, DIRECTORY);
    g_free (tmp);
  }

  DPRINT (N_("scandir %s\n"), cwd_cache->name);
  nentry = os_scandir (cwd_cache->name, &filename_list, &os_scandir_selector,
		       HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P));

  /* For displaying eagarly, I use two pass scanning. */
#ifdef DELETE_CORE
  /* before the traverse, check the existance of core */
  no_core = (os_file_kind ("core", TRUE) == NOT_EXIST);

  DPRINTIF (! no_core) (N_("Checking core: found\n"));
#endif

  /* 1st path: for collecting directories */
  DPRINT (N_("Scanning directory 1st path\n"));
  thumbnail_panel_set_info ("Reading the directory.");

  for (index = 0; index < nentry; index++)
    {
      gint	f_kind = NOT_EXIST;

      fname = filename_list[index];
      if (fname[0] == '.')
	continue;

      f_kind = os_file_kind (fname, FALSE);

      switch (f_kind)
	{
	case DIRECTORY:
	case SLNKDIR:
	  directory_cache_add_directory (cwd_cache, fname, f_kind);
	  break;
	default:
	  break;
	}
    }
  if (index_to_update < cwd_cache->ndir)
    {
      /* gtkW_preview_force_to_update (thumbnail_panel); */
      /* stop further update */
      index_to_update = -1;
    }

  /* 2nd path: for loading image files */
  DPRINT (N_("Scanning directory 2nd path\n"));
  thumbnail_panel_set_info (_("Reading the directory.."));

  {
    gint32 layer;
    /* Since many many troubles were/are/will be occurred from L&C dialog
       without any image, I add dummy image to gimp.
       Fri Nov 19 23:15:09 1999
    */

    dummy_image = gimp_image_new (32, 32, GIMP_RGB);
    layer = gimp_layer_new (dummy_image, "Guash is working now...", 32, 32,
			    GIMP_RGB_IMAGE, 100, GIMP_NORMAL_MODE);
    gimp_image_add_layer (dummy_image, layer, 0);
  }

  update_threshold = update_step = the_panel.ncol;
  for (index = 0; index < nentry; index++)
    {
      gint	f_kind = NOT_EXIST;
      gint	no_need_to_load = FALSE;

      if (index == update_threshold)
	{
	  DPRINT ("updating counter...\n");
	  sprintf (counter, _("Reading the directory (%02.0f%%)..."),
		   100 * (gfloat) index / (gfloat) nentry);
	  thumbnail_panel_set_info (counter);
	  update_threshold += update_step + 1;
	  if (emergency_termination)
	    {
	      DPRINT ("got emergency_termination\n");

	      gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
	      gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

	      if (0 <= dummy_image)
		gimp_image_delete (dummy_image);

	      RETURN FALSE;
	    }
	  DPRINT ("updating counter...done\n");
	}

      fname = filename_list[index];
      if (fname[0] == '.')
	continue;
      no_need_to_load = pathname_match_inhibit_suffix (fname);
      if (no_need_to_load && HAS_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
	continue;

      BUILD_ABSOLUTE (path, fname);

      f_kind = os_file_kind (path, FALSE);

      switch (f_kind)
	{
	case REGFILE:
	case SLNKFILE:
	  /* new semantics from 1.2.0 [Wed Sep  9 02:31:44 1998] */
	  if ((! no_need_to_load)
	      && (directory_cache_get_image (cwd_cache, path) != NULL))
	    directory_cache_add_image (cwd_cache, path, the_loaded_data, f_kind);
	  else if (HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
	    directory_cache_add_image (cwd_cache, path, NULL, f_kind);
	  else
	    {
	      DPRINT (N_("non image: %s\n"), fname);
	    }
	  break;
	default:
	  break;
	}
      if (index_to_update == directory_cache_num_entry (cwd_cache))
	{
	  /* gtkW_preview_force_to_update (thumbnail_panel); */
	  /* if the next entry is non-directory/image, another update is
	     occurred, unless reset index_to_update. */
	  index_to_update = -1;
	}
      DPRINT ("processed %s(index %d/%d, entry %d lock %d)\n", fname, index, nentry, directory_cache_num_entry (cwd_cache), with_lock);
    }
  g_free (filename_list);

  DPRINT (N_("end of scan\n"));

  gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);

#ifdef DELETE_CORE
  if (no_core && (os_file_kind ("core", TRUE) == REGFILE))
    {
      DPRINT (N_("unlink newly generated core file\n"));
      os_delete_file ("core");
      if (os_file_kind ("core", TRUE) == NOT_EXIST)
	{
	  DPRINT (N_("success to remove core\n"));
	}
      else
	  DPRINT (N_("fail to remove core!\n"));
    }
#endif
  cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);

  if (cwd_cache_npage () <= cwd_cache->display_page)
    {
      cwd_cache->display_page = cwd_cache_npage () - 1;
      display_request_redraw ();
    }
  else if (directory_cache_num_entry (cwd_cache) < index_to_update)
    {
    }
  else
    {
      /* Then thumbnail_panel doesn't know the true number of dir & image. */
      if ((0 < directory_cache_max_images)
	  && (directory_cache_max_images < directory_cache_num_entry (cwd_cache)))
	{
	  GCHAR	buffer[256];

	  sprintf (buffer,
		   _("Warning: # of files is beyond the display limit (%d)"),
		   directory_cache_max_images);
	  thumbnail_panel_set_info (buffer);
	  printf (buffer);
	  printf (".\nCheck guash's README file to control display limit\n");
	}
      else
	thumbnail_panel_set_info (NULL);
    }
  if (guash_discard_events ())
    thumbnail_panel_set_info (_("FYI, events during directory scan were discarded"));

  directory_cache_auto_shrink (cwd_cache, FALSE);

  if (0 <= dummy_image)
    gimp_image_delete (dummy_image);

  if (DURING_DRAG_MOTION_P)
    SET_ATTRIBUTE (cwd_cache, PURGED_P);

  display_request_redraw ();
  RETURN TRUE;
}

static gint
cwd_cache_update_after_file_operation (gint count,
				       GCHAR *operation,
				       GCHAR *first_success,
				       GCHAR *target)
{
  GCHAR	buffer[LINE_BUF_SIZE];

  G_ASSERT (0 < count);

  if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
    {
      directory_cache_update_selection (cwd_cache, SELECTION_DISORDER, 0);
      guash_update_cwd_cache (GC);
    }
  if (target)
    {
      if (count == 1)
	sprintf (buffer, _("%s was %s to %s"), first_success, operation, target);
      else
	sprintf (buffer, _("%d files were %s to %s"), count, operation, target);
    }
  else
    {
      if (count == 1)
	sprintf (buffer, _("%s was %s"), first_success, operation);
      else
	sprintf (buffer, _("%d files were %s"), count, operation);
    }

  thumbnail_panel_set_info (buffer);

  cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);

  return TRUE;
}

/*
 * DisplayRequest
 */
static void
display_request_initialize ()
{
  display_request.clear = FALSE;
  display_request.repaint = FALSE;
  display_request.repaint_rect = FALSE;
  display_request.x = 0;
  display_request.y = 0;
  display_request.width = 0;
  display_request.height = -1;
  display_request.finalize = FALSE;
} 

gint display_request_handler(void *);

static void
display_request_set_handler ()
{
  if (display_request_handler_id == 0)
    display_request_handler_id = gtk_idle_add (display_request_handler, NULL);
}

static void
display_request_redraw ()
{
  display_request.repaint = TRUE;
}

static void
display_request_redraw_rect (gint x, gint y, gint w, gint h)
{
#if 0
  if (the_panel.resized)
    /* don't return here. But I think some code required here for checking panel size */
#endif
  if ((! display_request.repaint) && (! display_request.repaint_rect)) 
    {
      display_request.repaint_rect = TRUE;
      display_request.x = x;
      display_request.y = y;
      display_request.width = w;
      display_request.height = h;
    }
  else if (display_request.repaint_rect)
    {
      gint max_x = MAX (display_request.x + display_request.width, x + w);
      gint max_y = MAX (display_request.y + display_request.height, y + h);

      /* merge two rects */
      display_request.x = MIN (display_request.x, x);
      display_request.y = MIN (display_request.y, y);
      display_request.width = max_x - display_request.x;
      display_request.height = max_y - display_request.y;
    }
}

static void
display_request_finalize ()
{
  display_request.finalize = TRUE;
}

static gint
display_request_handler (void * unused)
{
  display_request_handler_id  = 0;
  if (during_buildup_thumbnail_panel || (0 < with_lock))
    {
      /* gtk_idle_add (display_request_handler, NULL); */
      return FALSE;
    }
  if (display_request.repaint)
    {
      /* redraw whole panel */
      thumbnail_panel_update ();
      thumbnail_panel_finalize_update ();
    }
  else if (display_request.repaint_rect)
    {
      thumbnail_panel_update_rectangle (display_request.x,
					display_request.y,
					display_request.width,
					display_request.height);
      thumbnail_panel_finalize_update ();
    }
  else if (display_request.finalize)
    {
      thumbnail_panel_finalize_update ();
    }

  display_request_initialize ();

  /* set timeout again */
  /* gtk_idle_add (display_request_handler, NULL); */

  return FALSE;
}

/*
 * image_buffer
 */
static image_buffer *
image_buffer_new (gint width, gint height, gint ch)
{
  image_buffer	*new;

  DEBUGBLOCK (N_("image_buffer_new (%d, %d, %d)\n"), width, height, ch);

  new = g_new (image_buffer, 1);

  G_ASSERT (new);

  new->size = 0;
  new->data = NULL;
  image_buffer_resize (new, width, height, ch);

  RETURN new;
}

static void
image_buffer_resize (image_buffer *ibuf, gint width, gint height, gint ch)
{
  gint	size = width * height * ch;

  DEBUGBLOCK (N_("image_buffer_resize (%d %d %d: %d)\n"),
	      width, height, ch, ibuf->size);

  if ((ibuf->size < size) || (! ibuf->data))
    {
      if (ibuf->data)
	g_free (ibuf->data);
      ibuf->size = size;
      ibuf->data = g_new (guchar, size);
      G_ASSERT (ibuf->data);
      memset (ibuf->data, 0, ibuf->size);
    }
  ibuf->width = width;
  ibuf->height = height;
  ibuf->ch = ch;

  RETURN;
}

static void
image_buffer_free (image_buffer *ibuf)
{
  G_ASSERT (ibuf);

  if (ibuf->data)
    g_free (ibuf->data);

  g_free (ibuf);
}

static image_buffer *
image_buffer_new_with_header (gchar *data, gint width, gint height)
{
  image_buffer	*new;
  guchar	*ptr;

  DEBUGBLOCK (N_("image_buffer_new_with_header\n"));

  new = image_buffer_new (width, height, 3);
  for (ptr = new->data; ptr < new->data + new->size; ptr += 3)
    HEADER_PIXEL (data, ptr);

  RETURN new;
}

static image_buffer *
image_buffer_set_from_header (image_buffer *buffer,
			      gchar *data,
			      gint width,
			      gint height)
{
  guchar	*ptr;

  DEBUGBLOCK (N_("image_buffer_set_from_header"));

  image_buffer_resize (buffer, width, height, 3);

  for (ptr = buffer->data; ptr < buffer->data + buffer->size; ptr += 3)
    HEADER_PIXEL (data, ptr);

  RETURN buffer;
}

static void
image_buffer_copy (image_buffer *dest, image_buffer *src)
{
  G_ASSERT (src->data);

  DEBUGBLOCK (N_("image_buffer_copy\n"));

  image_buffer_resize (dest, src->width, src->height, src->ch);
  g_memmove (dest->data, src->data, src->width * src->height * src->ch);

  RETURN;
}

#ifdef DEFINE_OLD
static void
image_buffer_copy_from_drawable (image_buffer *ibuf, gint32 drawable_id)
{
  gint		x, y;
  GimpDrawable	*drawable;
  GimpPixelRgn	src_rgn;
  GCHAR		*src;
  gpointer	pr;
  gint		gap = 0;

  DEBUGBLOCK (N_("thumbnail_set_data_from_drawable\n"));

  image_buffer_resize (ibuf, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 3);

  drawable = gimp_drawable_get (drawable_id);
  G_ASSERT (drawable != NULL);

  ibuf->width = gimp_drawable_width (drawable_id);
  ibuf->height = gimp_drawable_height (drawable_id);

  gap = (gimp_drawable_has_alpha (drawable_id)) ? 1 : 0;
  gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0,
		       ibuf->width, ibuf->height,
		       FALSE, FALSE);
  pr = gimp_pixel_rgns_register (1, &src_rgn);

  for (; pr != NULL; pr = gimp_pixel_rgns_process (pr))
    {
      gint	gx = src_rgn.x;
      gint	gy = src_rgn.y;

      for (y = 0; y < src_rgn.h; y++)
	{
	  src = src_rgn.data + y * src_rgn.rowstride;

	  for (x = 0; x < src_rgn.w; x++)
	    {
	      guchar	*ch = src + (x * (3 + gap));
	      gint	offset = (gy + y) * ibuf->width * 3 + (gx + x) * 3;

	      ibuf->data[offset    ] = *ch;
	      ibuf->data[offset + 1] = *(ch + 1);
	      ibuf->data[offset + 2] = *(ch + 2);
	    }
	}
    }

  RETURN;
}
#endif

static void
image_buffer_copy_to_drawable (image_buffer *ibuf, gint32 drawable_id)
{
  guint		x, y;
  GimpDrawable	*drawable;
  GimpPixelRgn	src_rgn;
  GCHAR		*src;
  gpointer	pr;
  gint		gap = 0;

  DEBUGBLOCK (N_("thumbnail_set_data_from_drawable\n"));

  drawable = gimp_drawable_get (drawable_id);
  G_ASSERT (drawable != NULL);
  G_ASSERT (ibuf->width * ibuf->height * ibuf->ch <= ibuf->size);
  G_ASSERT (drawable->width == ibuf->width);
  G_ASSERT (drawable->height == ibuf->height);

  gap = (gimp_drawable_has_alpha (drawable_id)) ? 1 : 0;
  gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0,
		       ibuf->width, ibuf->height,
		       TRUE, FALSE);
  pr = gimp_pixel_rgns_register (1, &src_rgn);

  if (ibuf->ch == 1)
    for (; pr != NULL; pr = gimp_pixel_rgns_process (pr))
      {
	gint	gx = src_rgn.x;
	gint	gy = src_rgn.y;

	for (y = 0; y < ibuf->height; y++)
	  {
	    gint	offset = (gy + y) * ibuf->width + gx;
	    guchar	*ch = src_rgn.data + y * src_rgn.rowstride;

	    for (x = 0; x < ibuf->width; x++)
	      ch = indexed_to_rgb (ibuf->data[offset++], ch);
	    ch += gap;
	  }
      }
  else
    for (; pr != NULL; pr = gimp_pixel_rgns_process (pr))
      {
	gint	gx = src_rgn.x;
	gint	gy = src_rgn.y;

	for (y = 0; y < src_rgn.h; y++)
	  {
	    src = src_rgn.data + y * src_rgn.rowstride;

	    for (x = 0; x < src_rgn.w; x++)
	      {
		guchar	*ch = src + (x * (3 + gap));
		gint	offset = (gy + y) * ibuf->width * 3 + (gx + x) * 3;

		*ch       = ibuf->data[offset    ];
		*(ch + 1) = ibuf->data[offset + 1];
		*(ch + 2) = ibuf->data[offset + 2];
	      }
	  }
      }

  gimp_drawable_flush (drawable);
  /* gimp_drawable_merge_shadow (drawable->id, TRUE); */
  gimp_drawable_update (drawable->id, 0, 0, ibuf->width, ibuf->height);
  gimp_drawable_detach (drawable);

  RETURN;
}

/*
 * Thumbnail
 */
static void
thumbnail_initialize (Thumbnail *thumb)
{
  G_ASSERT (thumb);

  INITIALIZE_ATTRIBUTES (thumb);
  thumb->image = NULL;
  thumb->name = NULL;
  thumb->info = NULL;
}

static gint
thumbnail_compare_name (Thumbnail *a, Thumbnail *b)
{
  return strcmp (a->name, b->name);
}

static void
thumbnail_copy_data (Thumbnail *dest, Thumbnail *src, gint all_p)
{
  DEBUGBLOCK (N_("thumbnail_copy_data (to %sinitialized thumb.)\n"),
	      (dest->image ? "" : "un"));

  if (src->image != NULL)
    {
      if (! dest->image)
	{
	  dest->image = image_buffer_new (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 1);
	  DPRINT (N_("success to new\n"));
	}
      image_buffer_copy (dest->image, src->image);
      DPRINT (N_("success to copy to image_buffer\n"));
    }
  else
    {
      DPRINT (N_("src thumbnail has no image_buffer\n"));
    }

  if (all_p)
    {
      G_ASSERT (src->name);

      if (dest->name)
	g_free (dest->name);
      dest->name = g_strdup (src->name);

      G_ASSERT (src->info);

      if (dest->info)
	g_free (dest->info);
      dest->info = g_strdup (src->info);
    }
  RETURN;
}

static void
thumbnail_free_image (Thumbnail *thumb)
{
  DEBUGBLOCK (N_("thumbnail_free_image\n"));
  G_ASSERT (thumb);

  if (thumb->image)
    {
      image_buffer_free (thumb->image);
      thumb->image = NULL;
    }
  RETURN;
}

static gint
thumbnail_save_as_jpeg_thumbnail (Thumbnail *thumb, GCHAR *directory)
{
  image_buffer *ibuf;
  gint image_id;
  gint drawable_id;
  GimpParam*	return_vals;
  gint retvals;
  GCHAR	tmp[PATH_LENGTH];
  GCHAR	*filename;

  G_ASSERT (thumb);
  G_ASSERT (thumb->image);
  G_ASSERT (thumb->name);

  ibuf = thumb->image;

  if (directory != NULL)
    {
      BUILD_ABSOLUTE_PATH (tmp, directory, thumb->name);
    }
  else
    strcmp (tmp, thumb->name);

  filename = pathname_build_jpeg_thumbnail_filename (tmp);

  image_id = gimp_image_new (ibuf->width, ibuf->height, GIMP_RGB);
  gimp_image_undo_disable (image_id);
  drawable_id = gimp_layer_new (image_id, "Background",
				ibuf->width, ibuf->height,
				GIMP_RGB_IMAGE,
				0,
				GIMP_NORMAL_MODE);
  G_ASSERT (0 <= drawable_id);
  gimp_image_add_layer (image_id, drawable_id, 0);
  image_buffer_copy_to_drawable (ibuf, drawable_id);

  /* parameters are copied from DEFAULT_* in jpeg.c */
  return_vals = gimp_run_procedure ("file_jpeg_save",
				    &retvals,
				    GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE,
				    GIMP_PDB_IMAGE, image_id,
				    GIMP_PDB_DRAWABLE, drawable_id,
				    GIMP_PDB_STRING, filename,
				    GIMP_PDB_STRING, filename,
				    GIMP_PDB_FLOAT, 0.75, /* quality */
				    GIMP_PDB_FLOAT, 0.0, /* smoothing */
				    GIMP_PDB_INT32, TRUE, /* optimize */
				    GIMP_PDB_INT32, FALSE, /* progressive */
				    GIMP_PDB_STRING, "",	/* comment */
				    GIMP_PDB_INT32, 0, /* subsmp */
				    GIMP_PDB_INT32, 1, /* baselin */
				    GIMP_PDB_INT32, 0, /* restart */
				    GIMP_PDB_INT32, 0, /* DCT alorithm */
				    GIMP_PDB_END);

  gimp_destroy_params (return_vals, retvals);

  gimp_layer_delete (drawable_id);
  gimp_image_delete (image_id);
  g_free (filename);

  return TRUE;
}

/*
 * filename
 */

/* foo/bar.baz => foo/bar.baz/.xvpics */
static GCHAR *
pathname_build_thumbnail_dirname (GCHAR *dirname)
{
  gint	dir_len, cd_len, cp_len;
  GCHAR	*thumbnail_dcname;
  GCHAR	*thumbnail_dir = THUMBNAIL_DIR;

  dir_len = strlen (dirname);
  cd_len = strlen (THUMBNAIL_DIR);
  cp_len = dir_len + cd_len;
  thumbnail_dcname = g_new (GCHAR, cp_len + 1);

  g_memmove (thumbnail_dcname, dirname, dir_len);
  g_memmove (thumbnail_dcname + dir_len, thumbnail_dir, cd_len);
  thumbnail_dcname[cp_len] = 0;

  return thumbnail_dcname;
}

static GCHAR *
pathname_build_thumbnail_filename (GCHAR *filename)
{
  gint	index;
  gint	fname_len, tname_len;
  GCHAR	*thumbnail_fcname;
  GCHAR	*thumbnail_dir = THUMBNAIL_DIR;

  fname_len = strlen (filename);
  tname_len = fname_len + strlen (thumbnail_dir);
  index = pathname_get_last_separator_index (filename);

  G_ASSERT (0 <= index);

  thumbnail_fcname = g_new (GCHAR, tname_len + 1);
  g_memmove (thumbnail_fcname, filename, index);
  g_memmove (thumbnail_fcname + index, thumbnail_dir, strlen (thumbnail_dir));
  g_memmove (thumbnail_fcname + (index + strlen (thumbnail_dir)),
	     filename + index,
	     fname_len - index);
  thumbnail_fcname [tname_len] = 0;
  return thumbnail_fcname;
}

static GCHAR *
pathname_build_jpeg_thumbnail_filename (GCHAR *filename)
{
  gint	spos, ppos;
  gint	fname_len, tname_len;
  GCHAR	*thumbnail_fcname;
  const GCHAR *thumbnail_dir = THUMBNAIL_DIR;
  const gint thumbnail_dir_len = strlen (thumbnail_dir);
  const GCHAR *designator = "-thumb";
  const gint designator_len = strlen (designator);
  const GCHAR *file_type = "jpg";
  const gint file_type_len = strlen (file_type);
  GCHAR *ptr = NULL;

  fname_len = strlen (filename);

  spos = pathname_get_last_separator_index (filename);
  ppos = pathname_get_last_period_index (filename);
  if (ppos == 0)
    ppos = fname_len;

  tname_len = ppos + thumbnail_dir_len + designator_len + 1 + file_type_len;

  ptr = thumbnail_fcname = g_new (GCHAR, tname_len + 1);
  G_ASSERT (ptr != NULL);

#ifndef VISIBLE_HTML_THUMBNAIL_NAME
  if (0 < spos)
    {
      g_memmove (thumbnail_fcname, filename, spos); /* /dir */
      ptr += spos;
      g_memmove (ptr, thumbnail_dir, thumbnail_dir_len); /* /dir/.xvpict */
      ptr += thumbnail_dir_len;
    }
  else
#endif
    {
      g_memmove (ptr, thumbnail_dir + 1, thumbnail_dir_len - 1); /* .xvpict */
      ptr += thumbnail_dir_len - 1;
    }
  g_memmove (ptr++, "/", 1);				/* /dir/.xvpict/ */
  g_memmove (ptr, filename + spos + 1, ppos - spos - 1); /* /dir/.xvpict/foo */
  ptr += ppos - spos - 1;
  g_memmove (ptr, designator, designator_len);  /* /dir/.xvpict/foo-thumb */
  ptr += designator_len;
  g_memmove (ptr++, ".", 1);			/* /dir/.xvpict/foo-thumb. */
  g_memmove (ptr, file_type, file_type_len);   /* /dir/.xvpict/foo-thumb.jpg */
  ptr += file_type_len;

  thumbnail_fcname [tname_len] = 0;

  G_ASSERT (ptr == thumbnail_fcname + tname_len);

  return thumbnail_fcname;
}

static GCHAR *
pathname_get_canonical_name (GCHAR *name)
{
  GCHAR	cwd[PATH_LENGTH];
  GCHAR	*dir;

  dir = g_new (GCHAR, PATH_LENGTH);

  os_file_get_current_directory (cwd);
  if (os_file_change_current_directory (name) == -1)
    {
      g_free (dir);
      return NULL;
    }
  os_file_get_current_directory (dir);
  os_file_change_current_directory (cwd);

  return dir;
}

static gint
pathname_get_last_period_index (GCHAR *pathname)
{
  gint	index;
  gint	pname_len;

  pname_len = strlen (pathname);

  for (index = pname_len - 2; 0 <= index; index--)
    if (pathname[index] == '.') break;

  return index;
}

static gint
pathname_get_last_separator_index (GCHAR *pathname)
{
  /* return 0, if failed to search */
  gint	index;
  gint	pname_len;

  G_ASSERT (pathname);
  pname_len = strlen (pathname);

  for (index = pname_len - 2; 0 <= index; index--)
    if (pathname[index] == G_DIR_SEPARATOR) break;

  return index;
}

/* /foo/bar/baz => /foo/bar
  /foo/bar/baz/ => /foo/bar/baz (impossible case)
*/
static GCHAR *
pathname_get_directoryname (GCHAR *pathname)
{
  gint	pname_len, slash_pos;
  GCHAR	*dirname;

  __DEBUGBLOCK (N_("pathname_get_directoryname (\"%s\")\n"), pathname);
  G_ASSERT (pathname);

  pname_len = strlen (pathname);
  G_ASSERT (0 < pname_len);

  slash_pos = pathname_get_last_separator_index (pathname);

  if (slash_pos <= 0)
    RETURN ((strcmp (pathname, G_DIR_SEPARATOR_S) == 0) ? NULL : g_strdup (G_DIR_SEPARATOR_S));

  dirname = g_new (GCHAR, slash_pos + 1);
  g_memmove (dirname, pathname, slash_pos);
  dirname[slash_pos] = 0;

  RETURN dirname;
}

static GCHAR *
pathname_get_vaild_directoryname (GCHAR *pathname)
{
  GCHAR	*dirname;

  DEBUGBLOCK (N_("pathname_get_vaild_directoryname (\"%s\")\n"), pathname);
  G_ASSERT (pathname);

  dirname = pathname_get_directoryname (pathname);
  while (os_file_kind (dirname, TRUE) != DIRECTORY)
    {
      GCHAR *tmp;

      tmp = pathname_get_vaild_directoryname (dirname);
      g_free (dirname);
      dirname = tmp;
    }
  RETURN dirname;
}

/* foo/bar.baz => bar.baz */
static GCHAR *
pathname_get_basename (GCHAR *pathname)
{
  return pathname + pathname_get_last_separator_index (pathname) + 1;
}

/* pathname_is_valid_thumbnail_filename return nil if:
   1. thumbnail is older than the corresponding image file, or
   2. thumbnail file does not exist */
static gint
pathname_is_valid_thumbnail_filename (GCHAR *thumbnail_name)
{
  GCHAR		*file_name;
  struct stat	image_f, thumbnail_f;
  gint		stripper;
  gint		last_slash, parent_slash;

  stripper = strlen (THUMBNAIL_DIR);
  file_name = g_new (GCHAR, strlen (thumbnail_name) - stripper + 1);
  last_slash = pathname_get_last_separator_index (thumbnail_name);
  parent_slash = last_slash - strlen (THUMBNAIL_DIR);
  /* /.../dir/  */
  g_memmove (file_name, thumbnail_name, parent_slash);
  /* /.../dir/image.file */
  g_memmove (file_name + parent_slash,
	     thumbnail_name + last_slash,
	     strlen (thumbnail_name) - last_slash);
  /* /.../dir/image.file + TERMINATOR */
  file_name [strlen (thumbnail_name) - stripper] = 0;

  if (stat (file_name, &image_f) != 0)
    return FALSE;
  if (stat (thumbnail_name, &thumbnail_f) != 0)
    return FALSE;

  return (image_f.st_mtime <= thumbnail_f.st_mtime);
}

static gint
pathname_match_inhibit_suffix (GCHAR *pathname)
{
  gint	index = 0;
  gint	pathlen = strlen (pathname);

  for (; index < num_inhibit_suffix; index++)
    {
      GCHAR	*suffix = inhibit_suffix_table[index];
      gint	prelen = strlen (suffix);

      if (pathlen < prelen)
	continue;

      /* the last letter check. It will be much cheap test. */
      if (suffix[prelen - 1] == pathname[pathlen - 1])
	if (strcmp (suffix, pathname + (pathlen - prelen)) == 0)
	  return TRUE;
    }
  return FALSE;
}

/*
 * system call (mainly file operations) interface
 */
static gint
os_file_change_current_directory (GCHAR *pathname)
{
#ifdef NATIVE_WIN32
  /* //HB: On windoze e.g. chdir("D:") has a special meaning 
   * which isn't meant here
   */
  if (pathname && (2 == strlen(pathname)) && (':' == pathname[1]))
    return chdir ("\\");
#endif
  if (pathname)
    return chdir (pathname);
  else
    return TRUE;
}

static void
os_file_get_current_directory (GCHAR *buffer)
{
  GCHAR *dir;

  *buffer = 0;

  /* We don't use getcwd(3) on SUNOS, because, it does a popen("pwd")
   * and, if that wasn't bad enough, hangs in doing so.
   */
#if	defined (sun) && !defined (__SVR4)
  dir =  getwd (buffer);
#else	/* !sun */
  dir = getcwd (buffer, PATH_LENGTH - 1);
#endif	/* !sun */

#ifdef NATIVE_WIN32
  /* //HB: Only the root drive gets a backslash at it's end
   * Remove it; we don't need it.
   */
  if ((strlen(buffer) == 3) && (':' == buffer[1]) && ('\\' == buffer[2]))
    buffer[2] = 0;
#endif

  if (!dir || !*buffer)
    {
      /* hm, should we g_error() out here?
       * this can happen if e.g. "./" has mode \0000
       */
      buffer[0] = G_DIR_SEPARATOR;
      buffer[1] = 0;
    }
}

static gdouble
os_file_get_modify_timestamp (GCHAR *pathname)
{
  struct stat path_stat;

  stat (pathname, &path_stat);

  return (gdouble) path_stat.st_mtime;
}

/* if b has larger name than a, return 1 */
/* Warning: file existance check is omitted */
static gint
os_file_alphasort (GCHAR **a, GCHAR **b)
{
  return strcmp (*a, *b);
}

/* if b is newer than a, return 1 This means we get reverse ordered list. */
static gint
os_file_mtimesort (GCHAR **a, GCHAR **b)
{
  struct stat a_stat;
  struct stat b_stat;

  stat (*a, &a_stat);
  stat (*b, &b_stat);

  if (a_stat.st_mtime < b_stat.st_mtime)
    return 1;
  else if (a_stat.st_mtime == b_stat.st_mtime)
    return 0;
  else
    return -1;
}

static gint
os_scandir_selector (GCHAR *filename)
{
  return 1;
}

static gint
os_scandir (GCHAR *dir_name,
	    GCHAR ***filename_list,
	    scandir_selector_function selector,
	    gint sort_by_name_p)
{
  GCHAR		**list = NULL;
  struct dirent	*dirent_ptr;
  DIR		*directory;
  gint		entry_count = 32;
  gint		fail_p = FALSE;
  gint		i;

  DEBUGBLOCK (N_("os_scandir (\"%s\", ...)\n"), dir_name);

  *filename_list = NULL;
  if (! (directory = opendir (dir_name)))
    {
      closedir (directory);
      RETURN 0;
    }

  if (NULL == (list = g_new (GCHAR *, entry_count)))
    {
      closedir (directory);
      RETURN 0;
    }

  i = 0;
  while ((! fail_p) && (NULL != (dirent_ptr = readdir (directory))))
    {
      if (i == entry_count)
	{
	  gint	new_count = entry_count * 2;
	  GCHAR	**new_list = NULL;

	  /* expand list */
	  new_list = g_realloc (list, new_count * sizeof (GCHAR *));
	  if (new_list == NULL)
	    {
	      /* too much, try allocating less */
	      new_count = entry_count + 1;
	      new_list = g_realloc (list, new_count * sizeof (GCHAR *));
	      if (new_list == NULL)
		fail_p = TRUE;
	    }
	  list = new_list;
	  entry_count = new_count;
	}
      /* then copy the name to the cache */
      if (NULL == (list[i] = g_malloc (strlen (dirent_ptr->d_name) + 1)))
	fail_p = TRUE;
      else
	strcpy (list[i++], dirent_ptr->d_name);
    }
  closedir (directory);

  if (fail_p)
    {
      while (0 < i--)
	g_free (list[i]);
      g_free (list);

      RETURN 0;
    }

  if (i < entry_count)
    {
      list = g_realloc (list, i * sizeof (GCHAR *));
      entry_count = i;
    }

  qsort (list,
	 entry_count,
	 sizeof (GCHAR *),
	 (sort_compare_function) (sort_by_name_p ? &os_file_alphasort : &os_file_mtimesort));

  *filename_list = list;

  RETURN entry_count;
}

static gint
os_copy_file (GCHAR *filename, GCHAR *newname)
{
  gint		from;
  gint		to;
  gint		size;
  GCHAR		buffer[LINE_BUF_SIZE];
  mode_t	mode = NEW_FILE_MODE;

  if ((from = open (filename, O_RDONLY)) == -1)
    {
      perror ("os_copy_file: can't open source file");
      return FALSE;
    }
  if ((to = creat (newname, mode)) == -1)
    {
      close (from);
      perror ("os_copy_file: can't create destination file");
      return FALSE;
    }
  while (0 < (size = read (from, buffer, LINE_BUF_SIZE - 1)))
    write (to, buffer, size);

  close (from);
  close (to);

  return TRUE;
}

static gint
os_delete_file (GCHAR *filename)
{
  gint	type = os_file_kind (filename, TRUE);

  switch (type)
    {
    case REGFILE:
      return unlink (filename);
      break;
    case DIRECTORY:
      return rmdir (filename);
      break;
    default:
      return -1;
      break;
    }
}

static gint
os_file_is_writable (GCHAR *filename)
{
  struct stat	ent;
  gint		flag = FALSE;

  DEBUGBLOCK (N_("os_file_is_writable (\"%s\")\n"), filename);

  if (stat (filename, &ent) == 0)
    {
#ifdef S_IWUSR
      flag = S_IWUSR & ent.st_mode;
#else
      flag = S_IWRITE & ent.st_mode;
#endif

#if !defined (G_OS_WIN32)
      if (flag)
	flag = (ent.st_uid == getuid ());

      if (! flag)
	flag = (S_IWGRP & ent.st_mode) && (ent.st_gid == getgid ());
      if (! flag)
	flag = (S_IWOTH & ent.st_mode);
#else
      /* //HB: I don't know if only the M$VC compiler is bogus, but 
       * we need to return either FALSE==0 or TRUE==1
       */
      flag = (flag) ? TRUE : FALSE;
#endif
    }

  DPRINT (N_("%s is%s writable.\n"), filename, flag ? "" : " not");

  RETURN flag;
}

/* If SHRINK is non-FALSE, symbolic links are referred. */
static gint
os_file_kind (GCHAR *filename, gint shrink)
{
  struct stat	ent_sbuf;
  gint		flag = TRUE;			/*  fail safe */
  gint		symlink = FALSE;
  gint		size = 0;
  GCHAR		fname[PATH_LENGTH], tmp[PATH_LENGTH];

  G_ASSERT (filename);

#if !defined (G_OS_WIN32)
  if (*filename == G_DIR_SEPARATOR)
#else
  if ((strlen(filename) > 1) && (':' == filename[1]))
#endif
    strcpy (fname, filename);
  else
    {
      BUILD_ABSOLUTE (fname, filename);
    }
  while ((size = readlink (fname, tmp, PATH_LENGTH - 1)) != -1)
    {
      /* fname is a symbolic link. */
      symlink = TRUE;
      tmp[size] = 0;
      if (tmp[0] != G_DIR_SEPARATOR)
	{
	  GCHAR	*dir = NULL;
	  GCHAR	scratch[PATH_LENGTH];

	  dir = pathname_get_directoryname (fname);

	  BUILD_ABSOLUTE_PATH (scratch, dir, tmp);

	  strcpy (fname, scratch);
	  g_free (dir);
	}
      else
	{
	  strcpy (fname, tmp);
	}
    }

  if (stat (fname, &ent_sbuf) == 0)
    {
      /* something exists */
      if (S_ISDIR (ent_sbuf.st_mode))
	{
	  flag = DIRECTORY;
	}
      else if (S_ISREG (ent_sbuf.st_mode))
	flag = REGFILE;

      if ((! shrink) && symlink)
	{
	  switch (flag)
	    {
	    case REGFILE:
	      flag = SLNKFILE;
	      break;
	    case DIRECTORY:
	      flag = SLNKDIR;
	      break;
	    default:
	      break;
	    }
	}
    }
  else
    {
      /* error was occured */
      if (errno == ENOENT)
	flag = NOT_EXIST;
    }
  return flag;
}

static gint
os_file_size (GCHAR *filename)
{
  struct stat ent_sbuf;
  gint	size = 0;
  gint	f_size = -1;
  GCHAR	fname[PATH_LENGTH], tmp[PATH_LENGTH];

  strcpy (fname, filename);
  while ((size = readlink (fname, tmp, PATH_LENGTH - 1)) != -1)
    {
      /* fname is a symbolic link. */
      tmp[size] = 0;
      strcpy (fname, tmp);
    }
  if (stat (fname, &ent_sbuf) == 0)
    f_size = ent_sbuf.st_size;

  return f_size;
}

static gint
os_mkdir (GCHAR *pathname)
{
  GCHAR		info[256];
  mode_t	mode = NEW_DIRECTORY_MODE;
  gint		kind;

  kind = os_file_kind (pathname, TRUE);

  if (NOT_EXIST == kind)
    {
      if (os_make_directory (pathname, mode) != -1)
	{
	  GCHAR	*canonical;

	  canonical = pathname_get_canonical_name (pathname);
	  if (canonical != NULL)
	    {
	      GCHAR		*parent;
	      directory_cache	*dcache = NULL;

	      parent = pathname_get_directoryname (canonical);
	      dcache = guash_lookup_directory_cache (parent);

	      if (dcache != NULL)
		{
		  directory_cache_add_directory (dcache,
						 pathname_get_basename(canonical),
						 DIRECTORY);
		  dcache->timestamp = os_file_get_modify_timestamp (dcache->name);
		  SET_ATTRIBUTE (dcache, DISORDERED_P);
		}
	      if (dcache == cwd_cache)
		{
		  guash_update_cwd_cache (REDRAW);
		}

	      g_free (parent);
	    }
	  else
	    printf ("os_mkdir: can't move to the directory\n");
	  g_free (canonical);
	}
    }
  else
    {
      sprintf (info, _("%s already exists"), pathname);
      thumbnail_panel_set_info (info);
    }
  return (NOT_EXIST == kind);
}

static gint
os_make_directory (GCHAR *pathname, gint mode)
{
#ifdef NATIVE_WIN32
  return _mkdir (pathname);
#else
  return mkdir (pathname, mode);
#endif
}

static gint
os_rename_directory (GCHAR *oldname, GCHAR *newname)
{
  return rename (oldname, newname);
}

static gint
os_rename_file (GCHAR *filename, GCHAR *newname)
{
  return rename (filename, newname);
}

/* dialog stuff */
static int
DIALOG (gint restart)
{
  GtkWidget	*hbox;
  GtkWidget	*ps_box;
  GtkWidget	*thumbnail_panel_frame = NULL;
  GtkWidget	*button;
  gchar		**argv;
  gint		argc;

  DEBUGBLOCK (N_("DIALOG (building dialog window)\n"));

  argc = 1;
  argv = g_new (gchar *, 1);
  argv[0] = g_strdup (PLUG_IN_NAME);
  gdk_set_use_xshm (gimp_use_xshm ());

  /* Initialize Gtk toolkit */
  gtk_set_locale ();

#if GUASH_I18N
#if defined(HAVE_LC_MESSAGES) & defined(ENABLE_NLS)
  setlocale(LC_MESSAGES, "");
#endif
#if defined(ENABLE_NLS) & defined(LOCALEDIR)
  bindtextdomain("gimp-libgimp", LOCALEDIR);
  bindtextdomain("gimp-std-plugins", LOCALEDIR);
  bindtextdomain("guash", LOCALEDIR);
  textdomain("gimp-std-plugins");
#endif
#endif

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  dlg = gtk_dialog_new ();
  gtk_window_set_wmclass (GTK_WINDOW (dlg), "Guash", "Gimp");
  gtk_window_set_title (GTK_WINDOW (dlg), "Guash: initializing...");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) gtkW_close_callback, NULL);
  /* handle the wm close signal */
  gtk_signal_connect (GTK_OBJECT (dlg), "delete_event",
		      (GtkSignalFunc) gtkW_close_callback, NULL);

  /* set font */
  {
    GdkFont *font = GTK_WIDGET (dlg)->style->font;

    if (! font)
      {
	font = gdk_font_load (THUMBNAIL_PANEL_DEFAULT_FONT);
	if (! font)
	  {
	    printf ("Abort: no vaild font found!\n");
	    exit (0);
	  }
      }
    the_panel.font = font; 
  }

  /* set geometric parameters */
  the_panel.font_height = gdk_string_height (the_panel.font, "yX");
  DPRINT ("font height: %d\n", the_panel.font_height);
  the_panel.ncol = gtkW_parse_gimprc_gint ("guash-ncol",
					   NCOL_OF_THUMBNAIL_PANEL_DEFAULT);
  the_panel.ncol = CLAMP (the_panel.ncol, NCOL_OF_THUMBNAIL_PANEL_MIN, 10);
  the_panel.nrow = gtkW_parse_gimprc_gint ("guash-nrow",
					   NROW_OF_THUMBNAIL_PANEL_DEFAULT);
  the_panel.nrow = CLAMP (the_panel.nrow, NROW_OF_THUMBNAIL_PANEL_MIN, 10);
  the_panel.nthumb_in_page = the_panel.ncol * the_panel.nrow;
  the_panel.width = COL2WIDTH (the_panel.ncol);
  the_panel.height = ROW2HEIGHT (the_panel.nrow);

  /* Action Area */
  gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area), 0);
  gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dlg)->action_area), 0);

#ifdef USE_REDRAW_BUTTON
  button = gtk_button_new_with_label (_("Redraw"));
  GTK_CONTAINER (button)->border_width = 0;
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);
  gtk_widget_realize (button);
  gtk_signal_connect (GTK_OBJECT (button),
		      "clicked",
		      GTK_SIGNAL_FUNC (redraw_callback),
		      button);
#endif
  widget_for_selecion[0]
    = button
#ifdef GUASH_ENABLE_DND
    = gtk_button_new_with_label (_("Drag Me"));
#else
    = gtk_button_new_with_label (_("NOP"));
#endif
  GTK_CONTAINER (button)->border_width = 0;
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);
  gtk_widget_realize (button);
  gtk_signal_connect (GTK_OBJECT (button),
		      "clicked",
		      GTK_SIGNAL_FUNC (dnd_drag_button_callback),
		      button);
#ifdef GUASH_ENABLE_DND
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_begin",
		      GTK_SIGNAL_FUNC (dnd_drag_begin_callback),
		      NULL);
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_data_get",
		      GTK_SIGNAL_FUNC (dnd_drag_request_callback),
		      NULL);
  gtk_drag_source_set (button, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
		       dnd_target_table, dnd_n_targets,
		       GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_data_delete",
		      GTK_SIGNAL_FUNC (dnd_source_delete_data),
		      NULL);
#endif
  gtk_widget_set_sensitive (button, 0);

#if defined(USE_UPDATE_BUTTON) & (USE_UPDATE_BUTTON == 1)
  button = gtk_button_new_with_label (_("Update"));
  GTK_CONTAINER (button)->border_width = 0;
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) update_callback, dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);
#else
  widget_for_selecion[1] = button = gtk_button_new_with_label (_("Move"));
  GTK_CONTAINER (button)->border_width = 0;
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);
  gtk_widget_realize (button);
  gtk_signal_connect (GTK_OBJECT (button),
		      "clicked",
		      GTK_SIGNAL_FUNC (dnd_drag_button_callback),
		      button);
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_begin",
		      GTK_SIGNAL_FUNC (dnd_drag_begin_callback),
		      NULL);
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_data_get",
		      GTK_SIGNAL_FUNC (dnd_drag_request_callback),
		      NULL);
  gtk_drag_source_set (button, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
		       dnd_target_table, dnd_n_targets,
		       GDK_ACTION_MOVE);
  gtk_signal_connect (GTK_OBJECT (button),
		      "drag_data_delete",
		      GTK_SIGNAL_FUNC (dnd_source_delete_data),
		      NULL);
  gtk_widget_set_sensitive (button, 0);
#endif

  button = gtk_button_new_with_label (_("Help"));
  GTK_CONTAINER (button)->border_width = 0;
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) help_callback, dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);

  button = gtk_button_new_with_label (_("Close"));
  GTK_CONTAINER (button)->border_width = 0;
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtkW_close_callback,
			     GTK_OBJECT(dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);

  /* top row */
  hbox = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), FALSE, FALSE);

  cwd_label = gtk_label_new ("Gimp Users' Another SHell");
  gtk_widget_set_usize (cwd_label,
			LABEL_WIDTH (COL2WIDTH(NCOL_OF_THUMBNAIL_PANEL_MIN)),
			0);
  gtk_box_pack_start (GTK_BOX (hbox), cwd_label, TRUE, TRUE, LABEL_PADDING);
  gtk_misc_set_alignment (GTK_MISC (cwd_label), 0.0, 0.4);
  gtk_widget_show (cwd_label);

  button = gtk_button_new_with_label (_("Jump"));
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (directory_jump_callback),
		      NULL);
  gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_set_usize (button, JUMP_BUTTON_WIDTH, 0);
  gtk_widget_show (button);

  gtk_widget_show (hbox);

  /* 2nd row */
  ps_box = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), TRUE, TRUE);

  gdk_set_use_xshm (gimp_use_xshm ());
  gtk_preview_set_gamma (gimp_gamma ());
  {
    guchar *color_cube;
    color_cube = gimp_color_cube ();
    gtk_preview_set_color_cube (color_cube[0], color_cube[1],
				color_cube[2], color_cube[3]);
  }
  {
    GtkPreviewInfo *info;

    info = gtk_preview_get_info ();
    if ( info->visual->type != GDK_VISUAL_PSEUDO_COLOR)
      {
	/* rule of thumb: An extension should not share cmap with the gimp. */
	gtk_preview_set_install_cmap (gimp_install_cmap ());
      }
    gtk_widget_set_default_colormap (gtk_preview_get_cmap ());
    gtk_preview_reset ();
    gtk_widget_set_default_visual (gtk_preview_get_visual ());
  }

  thumbnail_panel = gtk_drawing_area_new ();
  gtk_drawing_area_size (GTK_DRAWING_AREA (thumbnail_panel),
			 the_panel.width,
			 the_panel.height);

  /* gtk_preview_set_expand (GTK_PREVIEW (thumbnail_panel), TRUE); */
  GTK_WIDGET_CLASS (GTK_OBJECT (thumbnail_panel)->klass)->size_request
    = thumbnail_panel_size_request;
  gtk_signal_connect_after (GTK_OBJECT (dlg), "size_allocate",
			    (GtkSignalFunc) thumbnail_panel_size_allocate,
			    NULL);
  /* 2.2.0 doesn't require the following. [Sat Dec 11 20:32:42 1999]
  GTK_WIDGET_CLASS (GTK_OBJECT (thumbnail_panel)->klass)->key_press_event
    = cursor_event_handler;
  */
  gtk_widget_set_events (thumbnail_panel, EVENT_MASK);
  gtk_widget_set_extension_events (thumbnail_panel, GDK_EXTENSION_EVENTS_ALL);
  GTK_WIDGET_SET_FLAGS (thumbnail_panel, GTK_CAN_FOCUS);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel), "event",
		      (GtkSignalFunc) thumbnail_panel_event_handler,
		      NULL);
  /* Hey, don't remove the following sentence!
     Desired behavior of Cursor keys depends on it, magically... */
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel), "key_press_event",
		      (GtkSignalFunc) cursor_event_handler,
		      NULL);
  gtk_widget_show (thumbnail_panel);

  thumbnail_panel_frame = gtkW_frame_new (NULL, NULL);
  gtk_container_border_width (GTK_CONTAINER (thumbnail_panel_frame), 0);
  gtk_container_add (GTK_CONTAINER (thumbnail_panel_frame), thumbnail_panel);
  gtk_widget_show (thumbnail_panel_frame);

  gtkW_ivscroll_entry_new (&widget_for_scroll[0],
			   &widget_for_scroll[1],
			   (GtkSignalFunc) gtkW_iscroll_update,
			   (GtkSignalFunc) gtkW_ientry_update,
			   &the_panel.current_page1,
			   1, 5, 1, &widget_pointer[0]);
  gtk_widget_set_usize (widget_for_scroll[0], SCROLLBAR_WIDTH, 0);
  gtk_widget_set_usize (widget_for_scroll[1], JUMP_BUTTON_WIDTH, 0);
  gtk_widget_show (widget_for_scroll[0]);
  gtk_widget_show (widget_for_scroll[1]);

  gtk_box_pack_start (GTK_BOX (ps_box), thumbnail_panel_frame, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (ps_box), widget_for_scroll[0], FALSE, FALSE, 0);

  gtk_widget_show (ps_box);

#if 0
  /* drag side */
  gtk_drag_source_set (thumbnail_panel, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
		       dnd_target_table, dnd_n_targets,
		       HAS_ATTRIBUTE (&VAL, DRAG_MOVE_P) ? GDK_ACTION_MOVE : GDK_ACTION_COPY | GDK_ACTION_MOVE);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_begin",
		      GTK_SIGNAL_FUNC (dnd_drag_begin_callback),
		      NULL);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_data_get",
		      GTK_SIGNAL_FUNC (dnd_drag_request_callback),
		      NULL);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_data_delete",
		      GTK_SIGNAL_FUNC (dnd_source_delete_data),
		      NULL);
#endif
  /* drop side */
  gtk_drag_dest_set (thumbnail_panel,
		     GTK_DEST_DEFAULT_ALL,
		     dnd_target_table, dnd_n_targets,
		     GDK_ACTION_COPY | GDK_ACTION_MOVE);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_drop",
		      GTK_SIGNAL_FUNC (dnd_drop_callback),
		      thumbnail_panel);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_data_received",
		      GTK_SIGNAL_FUNC (dnd_data_received),
		      thumbnail_panel);
  gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
		      "drag_motion",
		      GTK_SIGNAL_FUNC (dnd_drag_motion_callback),
		      thumbnail_panel);

  /* 3rd row */
  hbox = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), FALSE, FALSE);

  file_property = gtk_label_new ("Gimp Users' Another SHell: initializing...");
  gtk_widget_set_usize (file_property,
			LABEL_WIDTH (COL2WIDTH (NCOL_OF_THUMBNAIL_PANEL_MIN)),
			0);
  gtk_box_pack_start (GTK_BOX (hbox), file_property, TRUE, TRUE, LABEL_PADDING);
  gtk_misc_set_alignment (GTK_MISC (file_property), 0.0, 0.4);
  gtk_label_set_justify (GTK_LABEL (file_property), GTK_JUSTIFY_FILL);
  gtk_widget_show (file_property);

  gtk_box_pack_end (GTK_BOX (hbox), widget_for_scroll[1], FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  gtk_window_set_title (GTK_WINDOW (dlg), SHORT_NAME);
  gtk_widget_show (dlg);

  DPRINT (N_("Setting timer\n"));
  guash_is_initialized = FALSE;
  gtk_timeout_add (INITIALIZATION_DELAY, timer_initialize_guash, NULL);
  gtk_timeout_add (((VAL.last_dir_name[0] == 0) ?
		    INITIALIZATION_SLEEP_PERIOD : STARTUP_SLEEP_PERIOD),
		   timer_start_guash,
		   NULL);

  DEBUGEND;

  gtk_main ();
  gdk_flush ();
  return 1;
}

static gint
timer_initialize_guash (gpointer data)
{
  DEBUGBLOCK (N_("timer_initialize_guash\n"));

  if (guash_is_initialized)
    {
      if ((VAL.last_dir_name[0] == 0) && (cwd_cache == NULL))
	thumbnail_panel_show_banner_step2 ();
      return FALSE;
    }
  thumbnail_panel_initialize_banner ();
  thumbnail_panel_show_banner_step1 ();

  guash_parse_gimprc ();
  thumbnail_panel_create_menu ();
  guash_build_thumbnail_colormap ();

  guash_is_initialized = TRUE;
  gtk_timeout_add (BANNER_UP_PERIOD, timer_initialize_guash, NULL);

  the_panel.bg = NULL;
  the_panel.gc = NULL;
  the_panel.in_motion_p = FALSE;
  RESET_RUBBER_AREA;

  RETURN FALSE;
}

gint timer_directory_monitor(void* unused);

static gint
timer_start_guash (gpointer data)
{
  gboolean flag = FALSE;

  DEBUGBLOCK (N_("timer_start_guash\n"));

  if (! guash_is_initialized)
    {
      DPRINT (N_("not yet initialized. resleeping\n"));
      gtk_timeout_add (STARTUP_SLEEP_PERIOD, timer_start_guash, NULL);
      return FALSE;
    }
  if (VAL.last_dir_name[0] == 0)
    thumbnail_panel_show_banner_step3 ();
  thumbnail_panel_reinitialize_banner ();
  flag = (strlen (VAL.last_dir_name) == 0);

  the_loaded_data = g_new (Thumbnail, 1);
  thumbnail_initialize (the_loaded_data);
  the_loaded_data->image
    = image_buffer_new (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 3);
  INITIALIZE_ATTRIBUTES (the_loaded_data);

  guash_update_cwd_cache (REDRAW);

  if (flag)
    gimp_set_data (PLUG_IN_NAME, &VAL, sizeof (VALS));

  if (HAS_ATTRIBUTE (&VAL, MONITOR_P) && (0 < directory_monitor_interval))
    gtk_timeout_add (directory_monitor_interval,
		     timer_directory_monitor,
		     NULL);

  RETURN FALSE;
}

static gint
timer_directory_monitor (void* unused)
{
  gint		*image_ids;
  gint		num_images, index;
  gint		update_p = FALSE;

  DEBUGBLOCK (N_("timer_directory_monitor\n"));

  if (HAS_NO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
    {
      gtk_timeout_add (directory_monitor_interval,
		       timer_directory_monitor,
		       NULL);
      RETURN FALSE;
    }
  if (0 < with_lock)
    {
      DPRINT ("*** locked\n");
      RETURN FALSE;
    }
  if (DURING_DRAG_MOTION_P)
    {
      DPRINT ("*** stopped, during drag_motion\n");
      RETURN FALSE;
    }

  image_ids = gimp_image_list (&num_images);

  for (index = 0; index < num_images; index++)
    {
      GCHAR	*file_name = guash_get_image_filename (image_ids[index]);

      if (file_name && (os_file_kind (file_name, TRUE) == REGFILE))
	{
	  GCHAR			*path = NULL;
	  GCHAR			*thumbnail_fname = NULL;
	  directory_cache	*dcache = NULL;

	  path = pathname_get_directoryname (file_name);
	  G_ASSERT (path);
	  dcache = directory_cache_of (path);
	  thumbnail_fname = pathname_build_thumbnail_filename (file_name);
	  G_ASSERT (thumbnail_fname);

	  if (dcache
	      && dcache->savable
	      && HAS_ATTRIBUTE (dcache, FILED_P)
	      && (dcache->timestamp < os_file_get_modify_timestamp (file_name))
	      && (! pathname_is_valid_thumbnail_filename (thumbnail_fname)))
	    {
	      os_delete_file (thumbnail_fname);
	      if (directory_cache_update_thumbnail_for (cwd_cache, file_name, NULL))
		{
		  if (cwd_cache == dcache)
		    update_p = TRUE;
		}
	    }
	  g_free (path);
	}
      g_free (file_name);
    }

  if (cwd_cache->savable
      && (! during_buildup_thumbnail_panel)
      && HAS_ATTRIBUTE (cwd_cache, FILED_P)
      && (cwd_cache->timestamp < os_file_get_modify_timestamp (cwd_cache->name)))
    thumbnail_panel_set_info (_("FYI, the directory is just updated"));

  if (update_p)
    {
      display_request_redraw ();
      display_request_set_handler ();
      thumbnail_panel_set_info (_("Directory monitor found updated file(s)"));
    }

  gtk_timeout_add (directory_monitor_interval, timer_directory_monitor, NULL);

  RETURN FALSE;
}

static gint
about_dialog ()
{
  GtkWidget	*a_dlg;
  GtkWidget	*button;
  GtkWidget	*frame;
  GtkWidget	*hbox;
  GtkWidget	*table;
  GtkWidget	*hseparator;
  gint		index, i, nhelp;
  gint		align;

  nhelp = sizeof (help_document) / sizeof (help_document[0]);

  a_dlg = gtk_dialog_new ();
  gtk_window_set_wmclass (GTK_WINDOW (a_dlg), "Guash", "Gimp");
  gtk_window_set_title (GTK_WINDOW (a_dlg), "Guash help");
  gtk_window_position (GTK_WINDOW (a_dlg), GTK_WIN_POS_MOUSE);
  gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (a_dlg)->action_area),
			      gtkW_border_width);

  button = gtk_button_new_with_label (GUASH_TIMESTAMP);
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT(a_dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (a_dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  hbox = gtkW_hbox_new ((GTK_DIALOG (a_dlg)->vbox), FALSE, TRUE);
  frame = gtkW_frame_new (hbox, NULL);

  table = gtkW_table_new (frame, nhelp + 2, 3);
  index = 0;
  align = gtkW_align_x;
  gtkW_align_x = GTK_EXPAND|GTK_FILL;

  for (i = 0; i < nhelp; i++, index++)
    {
      if (help_document[i].action)
	{
	  gtkW_table_add_label (table, help_document[i].action,
				0, 1, index, help_document[i].flush_left);
	  if (help_document[i].condition)
	    gtkW_table_add_label (table, help_document[i].condition,
				  1, 2, index, help_document[i].flush_left);
	  gtkW_table_add_label (table, help_document[i].behavior,
				2, 3, index, help_document[i].flush_left);
	}
      else
	{
	  if (help_document[i].condition)
	    {
	      gtkW_table_add_label (table, help_document[i].condition,
				    0, 3, index, FALSE);
	    }
	  else			/* hseparator */
	    {
	      hseparator = gtk_hseparator_new ();
	      gtk_table_attach (GTK_TABLE (table), hseparator,
				0, 3, index, index + 1,
				GTK_FILL, GTK_FILL,
				gtkW_border_width, 2 * gtkW_border_width);
	      gtk_widget_show (hseparator);
	    }
	}
    }
  gtkW_align_x = align;
  gtk_widget_show (a_dlg);

  gdk_flush ();
  return 1;
}

/*
 * callbacks
 */

static void
menu_change_directory_callback (GtkWidget *widget, gpointer data)
{
  if (data != NULL)
    guash_change_current_directory ((GCHAR *) data, NULL);
}

static void
directory_jump_callback (GtkWidget *widget, gpointer data)
{
  GtkWidget	*jump_menu, *parents_menu, *menu_item;

  jump_menu = gtk_menu_new ();

  parents_menu = gtk_menu_item_new_with_label (_("Parents"));
  gtk_menu_append (GTK_MENU (jump_menu), parents_menu);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (parents_menu),
			     directory_cache_create_parents_menu ());
  gtk_widget_show (parents_menu);

  if (1 < directory_cache_table_size)
    {
      GtkWidget *history_menu;

      history_menu = gtk_menu_item_new_with_label (_("History"));
      gtk_menu_append (GTK_MENU (jump_menu), history_menu);
      gtk_menu_item_set_submenu (GTK_MENU_ITEM (history_menu),
				 directory_cache_create_history_menu ());
      gtk_widget_show (history_menu);
    }

  menu_item = gtk_menu_item_new_with_label (_("To ..."));
  gtk_menu_append (GTK_MENU (jump_menu), menu_item);
  gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
		      (GtkSignalFunc) fileselector_for_chdir_callback,
		      NULL);
  gtk_widget_show (menu_item);

  menu_item = gtk_menu_item_new_with_label (_("Close menu"));
  gtk_menu_append (GTK_MENU (jump_menu), menu_item);
  gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
		      (GtkSignalFunc) NULL,
		      NULL);
  gtk_widget_show (menu_item);

  gtk_menu_popup (GTK_MENU (jump_menu), NULL, NULL, NULL, NULL, 1, 0);
}

static void
parent_directory_callback (GtkWidget *widget, gpointer data)
{
  guash_change_current_directory ("..", NULL);
}

static void
forward_callback (GtkWidget *widget, gpointer data)
{
  thumbnail_panel_move_focus (1);
}

static void
select_and_forward_callback (GtkWidget *widget, gpointer data)
{
  gint	index = -1;
  Thumbnail	*thumbnail;

  if (selection_is_active ())
    {
      index = cwd_cache->last_focus + 1;

      if (! directory_cache_valid_index (cwd_cache, index))
	index = -1;
    }
  else
    if (0 < cwd_cache->nimage)
      index = cwd_cache->ndir;

  if (directory_cache_valid_image_index (cwd_cache, index))
    {
      selection_add_and_show (index);
      thumbnail = directory_cache_get_nth (cwd_cache, index);
      thumbnail_panel_set_info (thumbnail->info);
      cwd_cache->last_focus = index;
    }
  else
    thumbnail_panel_set_info (NULL);

  thumbnail_panel_update_sensitive_menu ();
}

static void
backward_callback (GtkWidget *widget, gpointer data)
{
  thumbnail_panel_move_focus (-1);
}

static void
next_callback (GtkWidget *widget, gpointer data)
{
  thumbnail_panel_move_focus (the_panel.ncol);
}

static void
prev_callback (GtkWidget *widget, gpointer data)
{
  thumbnail_panel_move_focus (- the_panel.ncol);
}

static void
next_page_callback (GtkWidget *widget, gpointer data)
{
  if (cwd_cache->display_page + 1 < cwd_cache_npage ())
    {
      cwd_cache->display_page++;
      display_request_redraw ();
      display_request_set_handler ();
    }
}

static void
prev_page_callback (GtkWidget *widget, gpointer data)
{
  if (0 < cwd_cache->display_page)
    {
      cwd_cache->display_page--;
      display_request_redraw ();
      display_request_set_handler ();
    }
}

static void
first_page_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache->display_page = 0;
  display_request_redraw ();
}

static void
last_page_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache->display_page = cwd_cache_npage () - 1;
  display_request_redraw ();
}

static void
show_comment_callback (GtkWidget *widget, gpointer data)
{
  if (directory_cache_valid_image_index (cwd_cache, cwd_cache->last_focus))
    {
      Thumbnail *selected;
      GCHAR	*str;

      selected = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus);

      if (selected == NULL)
	return;

      str = selected->info;
      /* OK. search the beginning of comment part in str */
      while (*str)
	if (*str++ == COMMENT_DELIMITOR)
	  {
	    if (*str == ' ')
	      {
		str++;
		break;
	      }
	    if (*str == 0)
	      break;
	  }
      if (*str == 0)
	str = _("no comment");

      if ((*str == 0) && (selected->info != NULL))
	str = selected->info;

      thumbnail_panel_set_info (str);
    }
}

static void
open_callback (GtkWidget *widget, gpointer data)
{
  selection_open_files ();
  guash_discard_events ();
}

static void
redraw_callback (GtkWidget *widget, gpointer data)
{
  display_request_redraw ();
}

static void
update_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("update_callback\n"));

  directory_cache_delete_invalid_cache_files (cwd_cache, FALSE);
  cwd_cache_update ();

  if (HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
    thumbnail_panel_set_info ("FYI, thumbnails are not updated in `display all files' mode");

  RETURN;
}

static void
toggle_display_mode_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate ());

  TOGGLE_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P);
  guash_update_cwd_cache (RESCAN);
  thumbnail_panel_set_info (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
			    ? _("Changed to `display only image' mode")
			    : _("Changed to `display all files' mode"));
}

/* ASSERT (cwd_cache_validate ()); */

static void
toggle_save_mode_callback (GtkWidget *widget, gpointer data)
{
  GCHAR *message;

  if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P)
      && HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
    {
      RESET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
      message = "Changed to `save thumbnails in xvpict format' mode";
    }
  else if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P)
	   && HAS_NO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
    {
      RESET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
      message = "Changed to `unsave thumbnails' mode";
    }
  else
    {
      SET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
      SET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
      message = "Changed to `save thumbnails in 16bit format' mode";
    }

  thumbnail_panel_set_info (_(message));
}

static void
toggle_sort_mode_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate ());

  TOGGLE_ATTRIBUTE (&VAL, SORT_BY_NAME_P);
  guash_update_cwd_cache (RESCAN);
  thumbnail_panel_set_info (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P)
			    ? _("Changed to `sort by name' mode")
			    : _("Changed to `sort by date' mode"));
}

static void
purge_selected_thumbnail_file_callback (GtkWidget *widget, gpointer data)
{
  selection_iterator	*iterator;
  Thumbnail		*selected;
  GCHAR			tmp[PATH_LENGTH];

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      GCHAR	*thumbnail_filename = NULL;

      BUILD_ABSOLUTE (tmp, selected->name);

      thumbnail_filename = pathname_build_thumbnail_filename (tmp);

      if (pathname_is_valid_thumbnail_filename (thumbnail_filename))
	os_delete_file (thumbnail_filename);
      g_free (thumbnail_filename);

      thumbnail_filename = pathname_build_jpeg_thumbnail_filename (tmp);
      G_ASSERT (thumbnail_filename);

      if (os_file_kind (thumbnail_filename, TRUE) == REGFILE)
	os_delete_file (thumbnail_filename);
      g_free (thumbnail_filename);
    }
  thumbnail_panel_set_info ("The thumbnail files of the selected images were deleted");
}

static void
purge_thumbnail_file_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate ());

  directory_cache_delete_invalid_cache_files (cwd_cache, TRUE);
}

#ifdef UNDER_DEVELOPMENT
static void
unimplement_callback (GtkWidget *widget, gpointer client_data)
{
  thumbnail_panel_set_info ("Not implemented yet");
}
#endif

static void
help_callback (GtkWidget *widget, gpointer client_data)
{
  about_dialog ();
}

static void
fileselector_for_copy_callback (GtkWidget *widget, gpointer client_data)
{
  GtkWidget *filesel;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  filesel = gtk_file_selection_new ("Copy selected images to");
  gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);

  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
		      "clicked", (GtkSignalFunc) copy_callback,
		      filesel);

  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (GTK_WINDOW (filesel)));

  if (selection_is_active ())
    {
      Thumbnail	*selected;
      GCHAR	tmp[PATH_LENGTH];

      selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);

      BUILD_ABSOLUTE (tmp, selected->name);

      gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
    }
  else if (strlen (VAL.last_dir_name) > 0)
    {
      gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
				       VAL.last_dir_name);
    }
  else
    {
      gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
				       cwd_cache->name);
    }
  gtk_widget_show (filesel);
}

static void
fileselector_for_move_callback (GtkWidget *widget, gpointer client_data)
{
  GtkWidget *filesel;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  if (! guash_confirm_operation ("Move"))
    return;

  filesel = gtk_file_selection_new ("Move selected images to");
  gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);

  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
		      "clicked", (GtkSignalFunc) move_callback,
		      filesel);

  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (GTK_WINDOW (filesel)));

  if (fileselector_last_pathname)
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
				     fileselector_last_pathname);
  gtk_widget_show (filesel);
}

static void
fileselector_for_move_directory_callback (GtkWidget *widget, gpointer client_data)
{
  GCHAR		current_dir[PATH_LENGTH];
  GtkWidget	*filesel;
  Thumbnail	*dir;

  ASSERT (cwd_cache_validate ());
  G_ASSERT (directory_cache_valid_subdirectory_index (cwd_cache, cwd_cache->last_focus_directory));

  if (! guash_confirm_operation ("Move"))
    return;

  filesel = gtk_file_selection_new ("Move the directory to");
  gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);

  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
		      "clicked", (GtkSignalFunc) move_directory_callback,
		      filesel);

  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (GTK_WINDOW (filesel)));

  dir = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus_directory);

  BUILD_ABSOLUTE (current_dir, dir->name);

  if (fileselector_last_pathname)
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), current_dir);
  gtk_widget_show (filesel);
}

static void
fileselector_for_chdir_callback (GtkWidget *widget, gpointer client_data)
{
  GtkWidget *filesel;

  ASSERT (cwd_cache_validate ());

  filesel = gtk_file_selection_new ("Change directory");
  gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);

  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
		      "clicked", (GtkSignalFunc) chdir_callback,
		      filesel);

  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (GTK_WINDOW (filesel)));
  {
    GCHAR	tmp[PATH_LENGTH];

    sprintf (tmp, "%s%c.", cwd_cache->name, G_DIR_SEPARATOR);
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
  }

  gtk_widget_show (filesel);
}

static void
fileselector_for_mkdir_callback (GtkWidget *widget, gpointer client_data)
{
  GtkWidget *filesel;

  ASSERT (cwd_cache_validate ());

  filesel = gtk_file_selection_new ("New directory");
  gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);

  {
    GCHAR	tmp[PATH_LENGTH];

    sprintf (tmp, "%s%c.", cwd_cache->name, G_DIR_SEPARATOR);
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
  }

  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
		      "clicked", (GtkSignalFunc) mkdir_callback,
		      filesel);

  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (GTK_WINDOW (filesel)));

  gtk_widget_show (filesel);
}

static void
copy_callback (GtkWidget *widget, gpointer data)
{
  GCHAR	*pathname, *tmp;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  pathname = g_new (GCHAR, strlen (tmp) + 1);
  strcpy (pathname, tmp);
  guash_set_fileselector_last_value (pathname);

  gtk_widget_destroy (GTK_WIDGET (data));

  selection_copy_files_to (pathname);

  g_free (pathname);
}

static void
move_callback (GtkWidget *widget, gpointer data)
{
  GCHAR	*pathname, *tmp;

  ASSERT (cwd_cache_validate () && selection_validate_image ());

  tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  pathname = g_new (GCHAR, strlen (tmp) + 1);
  strcpy (pathname, tmp);
  guash_set_fileselector_last_value (pathname);

  gtk_widget_destroy (GTK_WIDGET (data));

  selection_move_files_to (pathname);

  g_free (pathname);
}

static void
move_directory_callback (GtkWidget *widget, gpointer data)
{
  GCHAR	*pathname, *tmp;
  Thumbnail *dir;

  ASSERT (cwd_cache_validate ());
  ASSERT (directory_cache_valid_subdirectory_index (cwd_cache, cwd_cache->last_focus_directory));

  dir = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus_directory);

  tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  pathname = g_new (GCHAR, strlen (tmp) + 1);
  strcpy (pathname, tmp);
  guash_set_fileselector_last_value (pathname);

  gtk_widget_destroy (GTK_WIDGET (data));

  if (os_rename_directory (dir->name, pathname) < 0)
    {
      thumbnail_panel_set_info ("failed to move directory");
    }
  else
    {
      guash_add_entry (pathname, dir);
      SET_ATTRIBUTE (dir, DELETED_P);
      SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
      cwd_cache_update_after_file_operation (1, "moved", dir->name, pathname);
    }

  g_free (pathname);
}

static void
delete_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  if (HAS_ATTRIBUTE (&VAL, CONFIRM_P))
    {
      GCHAR buffer [LINE_BUF_SIZE];

      if (selection_length () == 1)
	{
	  Thumbnail	*selected;

	  selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);
	  sprintf (buffer, _(" Do you want to delete the selected image: %s? "),
		   selected->name);
	}
      else
	{
	  sprintf (buffer, _(" Do you want to delete the selected %d images? "),
		   selection_length ());
	}
      if (! gtkW_confirmor_dialog (TRUE, buffer, FALSE))
	return;
    }
  selection_delete_files ();
  guash_discard_events ();
}

static void
delete_directory_callback (GtkWidget *widget, gpointer data)
{
  GCHAR 	command[PATH_LENGTH + 10];
  Thumbnail	*dir;

  ASSERT (cwd_cache_validate ());
  G_ASSERT (0 < cwd_cache->last_focus_directory);
  G_ASSERT (directory_cache_valid_subdirectory_index (cwd_cache, cwd_cache->last_focus_directory));

  dir = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus_directory);

  if (1 || HAS_ATTRIBUTE (&VAL, CONFIRM_P))
    {
      GCHAR 	buffer[LINE_BUF_SIZE];

      sprintf (buffer, _(" Do you want to delete the directory: %s? "), dir->name);

      if (! gtkW_confirmor_dialog (TRUE, buffer, FALSE))
	return;
    }
  sprintf (command, "rm -fr %s", dir->name);
  thumbnail_panel_set_info (command);
  sleep (1);
  if (system (command) == -1)
    {
      thumbnail_panel_set_info ("failed to rm -fr");
    }
  else
    {
      SET_ATTRIBUTE (dir, DELETED_P);
      SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
      cwd_cache_update_after_file_operation (1, "deleted", dir->name, NULL);
    }
  return;
}

static void
select_all_callback (GtkWidget *widget, gpointer data)
{
  gint	index;
  gint	max = directory_cache_num_entry (cwd_cache);

  for (index = cwd_cache->ndir; index < max; index++)
    selection_add (index);
  thumbnail_panel_set_info (NULL);
  display_request_redraw ();
}

#ifdef VISIBLE_HTML_THUMBNAIL_NAME
static void
select_all_jpeg_thumbnail_callback (GtkWidget *widget, gpointer data)
{
  gint	index;
  gint	max = directory_cache_num_entry (cwd_cache);

  selection_reset ();

  for (index = cwd_cache->ndir; index < max; index++)
    {
      Thumbnail *thumb = directory_cache_get_nth (cwd_cache, index);
      const GCHAR	*jpeg_thumb_pattern = "-thumb.jpg";
      const gint	pattern_len = strlen (jpeg_thumb_pattern);

      if (strlen (thumb->name) < pattern_len)
	continue;
      if (!strcmp (jpeg_thumb_pattern, thumb->name + strlen (thumb->name) - pattern_len))
	selection_add (index);
    }
  thumbnail_panel_set_info (NULL);
}
#endif

static void
select_none_callback (GtkWidget *widget, gpointer data)
{
  thumbnail_panel_set_info (NULL);
  selection_reset ();
  display_request_redraw ();
}

static void
chdir_callback (GtkWidget *widget, gpointer data)
{
  GCHAR	*pathname, *tmp;
  gint	f_kind;

  ASSERT (cwd_cache_validate ());

  tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  pathname = g_new (GCHAR, strlen (tmp) + 1);
  strcpy (pathname, tmp);
  guash_set_fileselector_last_value (pathname);

  gtk_widget_destroy (GTK_WIDGET (data));

  tmp = pathname;
  f_kind = os_file_kind (tmp, TRUE);
  while ((f_kind == NOT_EXIST) || (f_kind == REGFILE))
    {
      tmp = pathname_get_directoryname (tmp);
      f_kind = os_file_kind (tmp, TRUE);
    }

  switch (os_file_kind (tmp, TRUE))
    {
    case DIRECTORY:
      guash_change_current_directory (tmp, NULL);
      break;
    default:
      thumbnail_panel_set_info (_("Can't move there"));
      break;
    }
  g_free (pathname);
}

static void
mkdir_callback (GtkWidget *widget, gpointer data)
{
  GCHAR	*pathname, *tmp;

  ASSERT (cwd_cache_validate ());

  tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  pathname = g_new (GCHAR, strlen (tmp) + 1);
  strcpy (pathname, tmp);
  gtk_widget_destroy (GTK_WIDGET (data));

  os_mkdir (pathname);

  /* guash_set_fileselector_last_value should be called after making the directory */
  guash_set_fileselector_last_value (pathname);

  g_free (pathname);
}

static void
mount_callback (GtkWidget *widget, gpointer data)
{
  GCHAR		dirname[PATH_LENGTH];
  GCHAR		command[LINE_BUF_SIZE];
  GCHAR		buffer[LINE_BUF_SIZE];
  Thumbnail	*dir = NULL;

  ASSERT (cwd_cache_validate ());
  G_ASSERT (directory_cache_valid_subdirectory_index (cwd_cache,
						      cwd_cache->last_focus_directory));

  dir = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus_directory);

  sprintf (buffer, "Trying to mount %s ...", dir->name);
  thumbnail_panel_set_info (buffer);

  BUILD_ABSOLUTE (dirname, dir->name);

  sprintf (command, "mount %s", dirname);

  if (system (command) == 0)
    {
      sprintf (buffer, "success to mount %s", dir->name);
      thumbnail_panel_set_info ("success to mount");

      guash_purge_subdirectory_information (buffer);
    }
  else
    {
      sprintf (buffer, "failed to mount %s", dir->name);
      thumbnail_panel_set_info (buffer);
    }
}

static void
unmount_callback (GtkWidget *widget, gpointer data)
{
  GCHAR		dirname[PATH_LENGTH];
  GCHAR		command[LINE_BUF_SIZE];
  GCHAR		buffer[LINE_BUF_SIZE];
  Thumbnail	*dir = NULL;

  ASSERT (cwd_cache_validate ());
  G_ASSERT (directory_cache_valid_subdirectory_index (cwd_cache,
						      cwd_cache->last_focus_directory));

  dir = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus_directory);

  sprintf (buffer, "Trying to unmount %s ...", dir->name);
  thumbnail_panel_set_info (buffer);

  BUILD_ABSOLUTE (dirname, dir->name);

  sprintf (command, "umount %s", dirname);

  if (system (command) == 0)
    {
      sprintf (buffer, "success to unmount %s", dir->name);
      thumbnail_panel_set_info (buffer);

      guash_purge_subdirectory_information (dirname);
    }
  else
    {
      sprintf (buffer, "failed to unmount %s", dir->name);
      thumbnail_panel_set_info (buffer);
    }
}

static void
selection_map_script_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  selection_map_script ();
}

static void
selection_map_unix_command_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  selection_map_unix_command ();
}

static void
selection_dump_as_html_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  selection_dump_as_html ();
}

static void
selection_dump_as_html_listing_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  selection_dump_as_html_listing ();
}

static void
selection_dump_as_html_guash_table_callback (GtkWidget *widget, gpointer data)
{
  ASSERT (cwd_cache_validate () && selection_validate_image ());

  selection_dump_as_html_guash_table ();
}

static void
jump_to_subdir1_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (1);
}

static void
jump_to_subdir2_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (2);
}

static void
jump_to_subdir3_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (3);
}

static void
jump_to_subdir4_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (4);
}

static void
jump_to_subdir5_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (5);
}

static void
jump_to_subdir6_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (6);
}

static void
jump_to_subdir7_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (7);
}

static void
jump_to_subdir8_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (8);
}

static void
jump_to_subdir9_callback (GtkWidget *widget, gpointer data)
{
  cwd_cache_jump_to_subdirectory_index (9);
}

static gint
thumbnail_panel_event_handler (GtkWidget *widget, GdkEvent *event)
{
  GdkEventButton *bevent;
  GdkEventExpose *eevent;
  /* GdkEventMotion *mevent; */
  Thumbnail	*thumbnail = NULL;
  gint		x, y, index;
  gboolean	should_be_update = TRUE;

  if (0 < with_lock)
    {
      return FALSE;
    }
  /* DEBUGBLOCK (N_("thumbnail_panel_event_handler(lock:%d)\n"), with_lock); */
  with_lock++;

  gtk_widget_get_pointer (widget, &x, &y);

  bevent = (GdkEventButton *) event;
  index = (cwd_cache && VALID_POS_P (x, y)) ? (POS_TO_INDEX (x, y)) : -1;
  if (directory_cache_valid_index (cwd_cache, index))
    thumbnail = directory_cache_get_nth (cwd_cache, index);

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      _DPRINT (N_("GDK_BUTTON PRESS\n"));
      /* reset some global variables */
      if (dnd_record.drag_data)
	g_string_free (dnd_record.drag_data, TRUE);
      dnd_record.drag_data = NULL;

      the_panel.in_motion_p = FALSE;

      switch (bevent->button)
	{
	case 1:
	  if (! directory_cache_valid_index (cwd_cache, index))
	    {
	      thumbnail_panel_set_info (NULL);
	      break;
	    }

	  if (HAS_ATTRIBUTE (thumbnail, DIRECTORY_P))
	    {
	      GCHAR	tmp[PATH_LENGTH];
	      GCHAR	*ptr = NULL;

	      if (HAS_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P))
		{
		  if (thumbnail->name)
		    ptr = thumbnail->name;
		  else if (!strcmp (cwd_cache->name, G_DIR_SEPARATOR_S)) 
		    /* This is the case of "/" */
		    ptr = G_DIR_SEPARATOR_S;
		  else
		    {
		      printf ("%s is invalid directory name!\n",
			      thumbnail->name);
		      G_ASSERT (FALSE);
		      ptr = cwd_cache->name;
		    }
		}
	      else
		{
		  BUILD_ABSOLUTE (tmp, thumbnail->name);
		  ptr = tmp;
		}

	      if (selection_is_active ())
		{
		  if (bevent->state & GDK_CONTROL_MASK)
		    {
		      selection_copy_files_to (ptr);
		    }
		  else if (bevent->state & GDK_SHIFT_MASK)
		    {
		      if (guash_confirm_operation ("Move"))
			selection_move_files_to (ptr);
		    }
		  else
		    {
		      guash_change_current_directory (ptr, thumbnail);
		    }
		}
	      else
		{
		  WATCH (guash_change_current_directory (ptr, thumbnail));
		}
	    }
	  else if (bevent->state & GDK_SHIFT_MASK)
	    {
	      if (selection_reverse_member (index))
		{
		  the_panel.in_motion_p = TRUE;
		  the_panel.start_x = x;
		  the_panel.start_y = y;
		  the_panel.end_x = x;
		  the_panel.end_y = y;
		}
	      if (selection_is_active ())
		thumbnail_panel_set_info (thumbnail->info);
	      else
		thumbnail_panel_set_info (NULL);
	    }
	  else if (selection_is_active () && selection_member_p (index))
	    selection_open_files ();
	  else
	    {
	      selection_reset ();
	      selection_add_and_show (index);
	      thumbnail_panel_set_info (thumbnail->info);
	      
	      the_panel.in_motion_p = TRUE;
	      the_panel.start_x = x;
	      the_panel.start_y = y;
	      the_panel.end_x = x;
	      the_panel.end_y = y;
	    }
	  break;
	case 2:
	  if (bevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
	    {
	      if (0 < cwd_cache->display_page)
		cwd_cache->display_page--;
	      else
		cwd_cache->display_page = cwd_cache_npage () - 1;
	    }
	  else
	    {
	      if (cwd_cache->display_page + 1 < cwd_cache_npage ())
		cwd_cache->display_page++;
	      else if (0 < cwd_cache->display_page)
		cwd_cache->display_page = 0;
	    }
	  display_request_redraw ();
	  break;
	case 3:
	  gtk_widget_set_sensitive (thumbnail_panel_directory_menu, FALSE);

	  if (bevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
	    gtk_menu_popup (GTK_MENU (thumbnail_panel_root_menu),
			    NULL, NULL, NULL, NULL, 3, bevent->time);
	  else if (selection_is_active ())
	    gtk_menu_popup (GTK_MENU (thumbnail_panel_selection_menu),
			    NULL, NULL, NULL, NULL, 3, bevent->time);
	  else if (directory_cache_valid_subdirectory_index (cwd_cache, index)
		   && HAS_NO_ATTRIBUTE (thumbnail, SYMLINK_P))
	    {
	      gtk_widget_set_sensitive (thumbnail_panel_directory_menu, TRUE);
	      cwd_cache->last_focus_directory = index;
	      gtk_menu_popup (GTK_MENU (thumbnail_panel_root_menu),
			      NULL, NULL, NULL, NULL, 3, bevent->time);
	    }
	  else
	    gtk_menu_popup (GTK_MENU (thumbnail_panel_root_menu),
			    NULL, NULL, NULL, NULL, 3, bevent->time);
	  display_request_redraw ();
	  should_be_update = FALSE;
	  break;
	case 4: /* wheel mouse */
	  prev_page_callback (NULL, NULL);
	  break;
	case 5:
	  next_page_callback (NULL, NULL);
	  break;
	default:
	  break;
	}
      break;
    case GDK_EXPOSE:
      eevent = (GdkEventExpose *) event;
      
      _DPRINT (N_("GDK_EXPOSE\n"));
      /* current string render works only if the window is on the top. Thus
	 we need repaint when the window receives GDK_EXPOSE.
	 Sun Aug 30 00:48:40 1998 */
      if (delayed_updating && (! during_buildup_thumbnail_panel))
	{
	  the_panel.resized = FALSE;
	  DPRINT (N_("resized: rendering...\n"));
	  display_request_redraw ();
	  DPRINT (N_("resized: rendering... done\n"));
	  gtk_widget_draw (thumbnail_panel, NULL);
	}
      display_request_redraw_rect (eevent->area.x, eevent->area.y,
				   eevent->area.width, eevent->area.height);
      /* from 0.99.5 [Sat Feb 28 06:30:45 1998] gtk_widget_show (dlg); */
      /* gdk_flush (); */
      break;
    case GDK_MOTION_NOTIFY:
      _DPRINT (N_("GDK_MOTION_NOTIFY\n"));
      if (the_panel.in_motion_p)
	{
	  if (! ((GdkEventMotion *)event)->state)
	    /* GDK_BUTTON_RELEASE was missed ! */
	    thumbnail_panel_finalize_rubber_band (TRUE);
      	  else
	    thumbnail_panel_update_rubber_band (x, y);
	}
      else
	{
	  should_be_update = FALSE;
	}
      break;
    case GDK_BUTTON_RELEASE:
      _DPRINT (N_("GDK_BUTTON_RELEASE\n"));
      if (the_panel.in_motion_p
	  && ((the_panel.start_x != the_panel.end_x)
	      || (the_panel.start_y != the_panel.end_y)))
	thumbnail_panel_finalize_rubber_band (TRUE);
      else
	thumbnail_panel_finalize_rubber_band (FALSE);
      break;
    case GDK_FOCUS_CHANGE:
      _DPRINT (N_("focus_change\n"));
      break;
    default:
      _DPRINT (N_("UNKNOWN EVENT\n"));
      should_be_update = FALSE; 
      break;
    }
  _DPRINT (N_("end of thumbnail_panel_event_handler\n"));
  if (should_be_update)
    display_request_set_handler ();

  with_lock--;
  return FALSE;
}

static gint
cursor_event_handler (GtkWidget *widget, GdkEventKey *event)
{
  GdkEventKey	*kevent;

  kevent = (GdkEventKey *) event;

  switch (kevent->keyval)
    {
    case GDK_Left:
      thumbnail_panel_move_focus (-1);
      break;
    case GDK_Right:
      thumbnail_panel_move_focus (1);
      break;
    case GDK_Up:
      thumbnail_panel_move_focus (- the_panel.ncol);
      break;
    case GDK_Down:
      thumbnail_panel_move_focus (the_panel.ncol);
      break;
    default:
      gtk_accel_groups_activate (GTK_OBJECT (thumbnail_panel),
				 kevent->keyval,
				 kevent->state);
      break;
    }
  gtk_widget_grab_focus (thumbnail_panel);
  return FALSE;
}

static void
dnd_drag_button_callback (GtkWidget *widget, GdkEvent *event)
{
#ifdef GUASH_ENABLE_DND
  thumbnail_panel_set_info (_("You failed to copy/move. Don't click but drag the button"));
#else
  thumbnail_panel_set_info (_("Install gtk+-1.2, then rebuild guash in order to use Drag-and-Drop."));
#endif
}

static void
dnd_drag_begin_callback (GtkWidget          *widget,
			 GdkDragContext     *context)
{
  selection_iterator	*iterator;
  Thumbnail		*selected;
  GString 		*str;

#ifndef GUASH_ENABLE_DND
  return;
#endif
  DEBUGBLOCK (N_("dnd_drag_begin_callback\n"));

  ASSERT (cwd_cache_validate () && selection_validate_image ());
  with_lock++;

  str = g_string_new ("");

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
    {
      g_string_append (str, GUASH_DND_SIGNATURE);
      g_string_append (str, cwd_cache->name);
      if (strcmp (cwd_cache->name, G_DIR_SEPARATOR_S))
	g_string_append_c (str, G_DIR_SEPARATOR);
      g_string_append (str, selected->name);
      g_string_append (str, "\r\n");	/* \r = 015 = ^M = 0xd */
    }

  DPRINT (N_("set data in dnd_drag_request_callback\n"));
  if (dnd_record.drag_data)
    g_string_free (dnd_record.drag_data, TRUE);
  dnd_record.drag_data = str;
  dnd_record.drag_start = cwd_cache;

  with_lock--;
  DPRINT (N_("set data into dnd_record (lock: %d)\n"), with_lock);
  RETURN;
}

static void
dnd_drag_request_callback (GtkWidget          *widget,
			   GdkDragContext     *context,
			   GtkSelectionData   *selection_data,
			   guint               info,
			   guint               time,
			   gpointer            data)
{
#ifndef GUASH_ENABLE_DND
  return;
#endif
  DEBUGBLOCK (N_("dnd_drag_request_callback\n"));

  if (! dnd_record.drag_data)
    {
      printf ("strange no dnd data found!\n");
      RETURN;
    }
  if (context->dest_window == thumbnail_panel->window)
    {
      dnd_record.internal_action = TRUE;
      DPRINT ("internal dnd\n");
      RETURN;
    }

  gtk_selection_data_set (selection_data,
			  selection_data->target,
			  8,
			  dnd_record.drag_data->str,
			  strlen (dnd_record.drag_data->str) + 1);

  g_string_free (dnd_record.drag_data, TRUE);
  dnd_record.drag_data = NULL;

  RETURN;
}

gint dnd_drag_motion_timeout (void* unused);

static void
dnd_drag_motion_callback (GtkWidget *widget, GdkDragContext *context,
			  gint x, gint y, guint time)
{
  Thumbnail *dir_thumb = NULL;
  gint index = (cwd_cache && VALID_POS_P (x, y)) ? (POS_TO_INDEX (x, y)) : -1;

  if ((index == 0)
      || directory_cache_valid_subdirectory_index (cwd_cache, index))
    dir_thumb = directory_cache_get_nth (cwd_cache, index);

  if (0 <= dnd_record.index)
    gtk_timeout_remove (dnd_record.timeout_handler);

  if (dir_thumb)
    {
      dnd_record.time = time;
      dnd_record.drag_start = cwd_cache;

      if (index != dnd_record.index)
	dnd_record.index = index;

      dnd_record.timeout_handler
	= gtk_timeout_add (DND_DIRECTORY_CHANGE_TIMEOUT,
			   dnd_drag_motion_timeout,
			   NULL);
    }
  else
    {
      dnd_record.index = -1;
    }
}

static gint
dnd_drag_motion_timeout (void* unused)
{
  if (dnd_record.drag_data)
    {
      thumbnail_panel_set_info ("Timeout traverse works only for data from another program");
    }
  else if (DURING_DRAG_MOTION_P && (dnd_record.drag_start == cwd_cache))
    {
      Thumbnail *thumbnail = NULL;
      GCHAR	tmp[PATH_LENGTH];
      GCHAR	*ptr = NULL;

      thumbnail = directory_cache_get_nth (cwd_cache, dnd_record.index);

      if (dnd_record.index == 0)		/* parent directory */
	{
	  if (thumbnail->name)
	    ptr = thumbnail->name;
	  else if (!strcmp (cwd_cache->name, G_DIR_SEPARATOR_S)) 
	    /* This is the case of "/" */
	    ptr = G_DIR_SEPARATOR_S;
	  else
	    {
	      printf ("%s is invalid directory name!\n", thumbnail->name);
	      G_ASSERT (FALSE);
	      ptr = cwd_cache->name;
	    }
	}
      else
	{
	  BUILD_ABSOLUTE (tmp, thumbnail->name);
	  ptr = tmp;
	}
      guash_change_current_directory (ptr, thumbnail);
    }
  dnd_record.index = -1;
  dnd_record.time = 0;
  dnd_record.drag_start = NULL;
  return FALSE;
}

static void
dnd_source_delete_data  (GtkWidget          *widget,
			 GdkDragContext     *context,
			 gpointer            data)
{
  gint	deleted = 0;
  gint	maybe_failed = 0;
  selection_iterator	*iterator;
  selection_iterator_entry 	*entry;
  GCHAR				first_file[LINE_BUF_SIZE];

#ifndef GUASH_ENABLE_DND
  return;
#endif
  DEBUGBLOCK (N_("dnd_source_delete_data\n"));
  if (dnd_record.drag_data)
    g_string_free (dnd_record.drag_data, TRUE);
  dnd_record.drag_data = NULL;

  with_lock++;

  if (context->action != GDK_ACTION_MOVE)
    {
      with_lock--;
      RETURN;
    }

  DPRINT (N_("action is GDK_ACTION_MOVE: update thumbnail_panel\n"));
  /* selection_delete_files (); */
  /* Here we must check whether the files exist. */

  iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  while (NULL != (entry = selection_iterator_get_next_entry (iterator)))
    {
      Thumbnail *selected = entry->thumbnail;
      GCHAR	pathname[PATH_LENGTH];

      BUILD_ABSOLUTE (pathname, selected->name);

      if (os_file_kind (pathname, TRUE) == NOT_EXIST)
	{
	  DPRINT ("%s was deleted\n", pathname);
	  if (deleted++ == 0)
	    strcpy (first_file, selected->name);
	  selection_delete (entry->index);
	  SET_ATTRIBUTE (selected, DELETED_P);
	}
      else
	{
	  DPRINT ("%s wasn't deleted\n", pathname);
	  maybe_failed++;
	}
    }
  if (0 < deleted)
    {
      GCHAR	aux[LINE_BUF_SIZE];

      SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
      if (0 < maybe_failed)
	sprintf (aux, "moved (with %d fails)", maybe_failed);
      else
	strcpy (aux, "moved");
      cwd_cache_update_after_file_operation (deleted, aux, first_file, NULL);
    }
  else
    thumbnail_panel_set_info (_("Failed to move (permission problem?)"));

  with_lock--;
  RETURN;
}

static void
dnd_data_received  (GtkWidget          *widget,
		    GdkDragContext     *context,
		    gint                x,
		    gint                y,
		    GtkSelectionData   *data,
		    guint               info,
		    guint               time)
{
  GCHAR	*ptr = NULL;
  GCHAR	dest[PATH_LENGTH];
  GCHAR	*subdir_p = NULL;
  gint	index;

#ifndef GUASH_ENABLE_DND
  return;
#endif
  dnd_record.index = -1;
  ASSERT (cwd_cache_validate ());

  DEBUGBLOCK (N_("dnd_data_received\n"));
  with_lock++;

  ptr = (GCHAR *) data->data;

  index = VALID_POS_P (x, y) ? POS_TO_INDEX (x, y): -1;

  if (directory_cache_valid_index (cwd_cache, index))
    {
      Thumbnail	*thumbnail = directory_cache_get_nth (cwd_cache, index);

      if (HAS_ATTRIBUTE (thumbnail, DIRECTORY_P))
	{
	  if (HAS_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P))
	    {
	      sprintf (dest, "%s", thumbnail->name);
	      subdir_p = "the parent directory";
	    }
	  else
	    {
	      BUILD_ABSOLUTE (dest, thumbnail->name);

	      subdir_p = thumbnail->name;
	    }
	}
      else
	strcpy (dest, cwd_cache->name);
    }
  else
    strcpy (dest, cwd_cache->name);

  {
    GCHAR	*message;

    if (subdir_p == NULL)
      message = "current directory";
    else
      message = subdir_p;

    switch (context->action)
      {
      case GDK_ACTION_DEFAULT:
      case GDK_ACTION_COPY:
	if (dnd_copy_files_to (ptr, dest, message, (subdir_p == NULL), FALSE))
	  gtk_drag_finish (context, TRUE, FALSE, time);
	else
	  gtk_drag_finish (context, FALSE, FALSE, time);
	break;
      case GDK_ACTION_MOVE:
	if (dnd_copy_files_to (ptr, dest, message, (subdir_p == NULL), TRUE))
	  gtk_drag_finish (context, TRUE, TRUE, time);
	else
	  gtk_drag_finish (context, FALSE, TRUE, time);
	break;
      case GDK_ACTION_LINK:
      case GDK_ACTION_ASK:
      default:
	gtk_drag_finish (context, TRUE, FALSE, time);
	break;
      }
  }
  with_lock--;
  DPRINT ("done (lock %d)\n", with_lock);
  RETURN;
}

static gboolean
dnd_drop_callback (GtkWidget		*widget,
		   GdkDragContext	*context,
		   gint			x,
		   gint			y,
		   guint		time)
{
#ifndef GUASH_ENABLE_DND
  return FALSE;
#endif
  DEBUGBLOCK (N_("dnd_drop\n"));
  RETURN FALSE;

  DPRINTIF (context->targets) (N_("set data\n"));

  if (context->targets)
    gtk_drag_get_data (widget, context,
		       GPOINTER_TO_INT (context->targets->data),
		       time);

  RETURN FALSE;
}

static gint
dnd_copy_files_to (GCHAR *buffer, GCHAR *pathname, GCHAR *dir, gint cwd_p, gint delete)
{
  GCHAR			info[LINE_BUF_SIZE];
  GCHAR			first_file[LINE_BUF_SIZE];
  GCHAR			*ptr = buffer;
  gint			kind = NOT_EXIST;
  gint			success = 0;
  gint			fail = FALSE;
  gint			sig_len = strlen (GUASH_DND_SIGNATURE);
  directory_cache	*dcache = NULL;

 __DEBUGBLOCK (N_("dnd_copy_files_to (\"%s\", \"%s\", \"%s\", %d, %d)\n"),
	      buffer, pathname, dir, cwd_p, delete);

  dcache = guash_lookup_directory_cache (pathname);

  while (*ptr)
    {
      gint	index = 0;

      kind = os_file_kind (pathname, TRUE);
      while ((ptr[index] != 0) && (ptr[index] != '\n'))
	index++;

      if ((kind == NOT_EXIST) || (kind == DIRECTORY))
	{
	  GCHAR	name_buffer[PATH_LENGTH];
	  GCHAR *name = name_buffer;
	  GCHAR	new[PATH_LENGTH];
	  GCHAR	*file_basename;

	  g_memmove (name_buffer, ptr, index);

	  if (name_buffer[index -1] == 0xd)
	    name_buffer[index -1] = 0;
	  else
	    name_buffer[index] = 0;

	  if ((sig_len < strlen (name)) &&
	      (! strncmp (name, GUASH_DND_SIGNATURE, sig_len)))
	    name += sig_len;

	  DPRINT (N_("source: \"%s\"\n"), name);
	  file_basename = pathname_get_basename (name);

	  if (kind == DIRECTORY)
	    {
	      if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
		sprintf (new, "%s%c%s", pathname, G_DIR_SEPARATOR, file_basename);
	      else
		sprintf (new, "%s%s", pathname, file_basename);
	    }
	  else
	    sprintf (new, "%s", pathname);

	  DPRINT (N_("destination: \"%s\"\n"), new);
	  /* try to copy `name' to `new->str' */
	  if (strcmp (name, new) == 0)
	    {
	      DPRINT ("abort: %s to the identical destination\n", name);
	      fail = TRUE;
	      sprintf (info, _("The destination is identical to %s."), name);
	      gtkW_message_dialog (TRUE, info);
	    }
	  else if (os_file_kind (new, TRUE) != NOT_EXIST)
	    {
	      DPRINT ("abort: %s exists\n", new);
	      fail = TRUE;
	      sprintf (info, _("%s already exists"), new);
	      gtkW_message_dialog (TRUE, info);
	    }
	  else if (delete && guash_move_image_file (name, new))
	    {
	      DPRINT ("moved\n");
	      if (success++ == 0)
		strcpy (first_file, file_basename);
	      if (dcache != NULL)
		directory_cache_update_thumbnail_for (dcache, new, NULL);
	    }
	  else if ((! delete) && guash_copy_image_file (name, new))
	    {
	      DPRINT ("copied\n");
	      if (success++ == 0)
		strcpy (first_file, file_basename);
	      if (dcache != NULL)
		directory_cache_update_thumbnail_for (dcache, new, NULL);
	    }
	  else
	    {
	      DPRINT ("abort: unknown error\n");
	      fail = TRUE;
	    }
	}
      else if (kind == REGFILE)
	{
	  DPRINT ("destination file(%s) exists.\n", pathname);
	  fail = TRUE;
	  sprintf (info, _("%s already exists"), pathname);
	  gtkW_message_dialog (TRUE, info);
	}
      if (ptr[index] == 0)
	break;
      else
	ptr += index + 1;
    }

  if (0 < success)
    {
      if (dcache != NULL)
	dcache->timestamp = os_file_get_modify_timestamp (dcache->name);
      cwd_cache_update_after_file_operation (success,
					     (delete ? "moved" : "copied"),
					     first_file, dir);
    }
  else
    thumbnail_panel_set_info (_("Failed to copy"));

  DPRINT ("done (lock %d)\n", with_lock);
  RETURN (! fail);
}

/*
 * start of GtkW lib
 */
static void
gtkW_close_callback (GtkWidget *widget,
		     gpointer   data)
{
  if (widget == dlg)
    emergency_termination = TRUE;
  gtk_main_quit ();
}

static void
gtkW_message_dialog (gint gtk_was_initialized, GCHAR *message)
{
  GtkWidget	*dlg;
  GtkWidget	*table;
  GtkWidget	*label;
  gchar		**argv;
  gint		argc;

  if (! gtk_was_initialized)
    {
      argc = 1;
      argv = g_new (gchar *, 1);
      argv[0] = g_strdup (PLUG_IN_NAME);
      gtk_init (&argc, &argv);
      gtk_rc_parse (gimp_gtkrc ());
    }

  dlg = gtkW_message_dialog_new (PLUG_IN_NAME);
  gtk_window_set_wmclass (GTK_WINDOW (dlg), "Guash", "Gimp");

  table = gtkW_table_new (GTK_DIALOG (dlg)->vbox, 1, 1);

  label = gtk_label_new (message);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND,
		    0, 0, 0);

  gtk_widget_show (label);
  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();
}

static GtkWidget *
gtkW_message_dialog_new (GCHAR *name)
{
  GtkWidget *dlg, *button;

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), name);
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) gtkW_close_callback, NULL);
  gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area),
			      gtkW_border_width);

  /* Action Area */
  button = gtk_button_new_with_label (_("OK"));
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  return dlg;
}

static gint
gtkW_confirmor_dialog (gint gtk_was_initialized, GCHAR *message, gint default_value)
{
  GtkWidget	*dlg;
  GtkWidget	*table;
  GtkWidget	*label;
  gtkW_widget_table	wtable;
  gchar		**argv;
  gint		argc;

  if (! gtk_was_initialized)
    {
      argc = 1;
      argv = g_new (gchar *, 1);
      argv[0] = g_strdup (PLUG_IN_NAME);
      gtk_init (&argc, &argv);
      gtk_rc_parse (gimp_gtkrc ());
    }

  dlg = gtkW_confirmor_new (PLUG_IN_NAME, default_value, &wtable);

  table = gtkW_table_new (GTK_DIALOG (dlg)->vbox, 1, 1);

  label = gtk_label_new (message);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND,
		    0, 0, 0);

  gtk_widget_show (label);
  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();
  return (gint) wtable.value;
}

static GtkWidget *
gtkW_confirmor_new (GCHAR *name, gint default_value, gtkW_widget_table *table)
{
  GtkWidget	*dlg, *button;

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), name);
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) gtkW_close_callback, NULL);
  gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area),
			      gtkW_border_width);
  table->widget = dlg;

  /* Action Area */
  button = gtk_button_new_with_label (_("Yes"));
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) gtkW_confirmor_yes,
		      (gpointer) table);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  if (default_value)
    {
      gtk_widget_grab_default (button);
    }
  gtk_widget_show (button);

  button = gtk_button_new_with_label (_("No"));
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) gtkW_confirmor_no,
		      (gpointer) table);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
		      TRUE, TRUE, 0);
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  if (! default_value)
    {
      gtk_widget_grab_default (button);
    }
  gtk_widget_show (button);

  return dlg;
}

static void
gtkW_confirmor_yes (GtkWidget *widget, gpointer data)
{
  gtkW_widget_table *table;

  table = (gtkW_widget_table *)data;
  table->value = (gpointer) TRUE;
  gtk_widget_destroy ((GtkWidget *) (table->widget));
}

static void
gtkW_confirmor_no (GtkWidget *widget, gpointer data)
{
  gtkW_widget_table *table;

  table = (gtkW_widget_table *)data;
  table->value = (gpointer) FALSE;
  gtk_widget_destroy ((GtkWidget *) (table->widget));
}

static GtkWidget *
gtkW_frame_new (GtkWidget *parent, GCHAR *name)
{
  GtkWidget *frame;

  frame = gtk_frame_new (name);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), gtkW_frame_shadow_type);
  gtk_container_border_width (GTK_CONTAINER (frame), gtkW_border_width);
  if (parent != NULL)
    gtk_box_pack_start (GTK_BOX(parent), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  return frame;
}

static GtkWidget *
gtkW_hbox_new (GtkWidget *parent, gint expand, gint fill)
{
  GtkWidget	*hbox;

  hbox = gtk_hbox_new (FALSE, 2);
  gtk_container_border_width (GTK_CONTAINER (hbox), gtkW_border_width);
  if (parent)
    {
      gtk_box_pack_start (GTK_BOX (parent), hbox, expand, fill,
			  gtkW_border_width);
      /* gtk_container_add (GTK_CONTAINER (parent), hbox); */
    }
  gtk_widget_show (hbox);

  return hbox;
}

static GtkWidget *
gtkW_table_new (GtkWidget *parent, gint col, gint row)
{
  GtkWidget	*table;

  table = gtk_table_new (col,row, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), gtkW_border_width);
  gtk_container_add (GTK_CONTAINER (parent), table);
  gtk_widget_show (table);

  return table;
}

static GtkWidget *
gtkW_table_add_label (GtkWidget	*table,
		      GCHAR	*text,
		      gint	x0,
		      gint	x1,
		      gint	y,
		      gint	flush_left)
{
  GtkWidget *label;

  label = gtk_label_new (text);
  if (flush_left)
    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
  gtk_table_attach (GTK_TABLE(table), label, x0, x1, y, y+1,
		    gtkW_align_x, gtkW_align_y, 5, 0);
  gtk_widget_show (label);

  return label;
}

static void
gtkW_iscroll_entry_change_value (gtkW_widget_table *wtable)
{
  GtkWidget	*entry;
  gchar		buffer[32];
  GtkAdjustment *adjustment = (GtkAdjustment *) (wtable->widget);

  if (! cwd_cache)
    return;
  /*  adustment->value is double, that is not precise to hold long interger. */
  adjustment->value = * (gint *) (wtable->value);
  gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
  entry = gtk_object_get_user_data (GTK_OBJECT (adjustment));
  if (entry)
    {
      sprintf (buffer, "%d", (gint) adjustment->value);
      gtk_entry_set_text (GTK_ENTRY (entry), buffer);
    }
}

static void
gtkW_iscroll_update (GtkAdjustment *adjustment,
		     gpointer       data)
{
  GtkWidget	*entry;
  gchar		buffer[32];
  gint		*val;

  if (! cwd_cache)
    return;
  val = data;
  if (*val != (gint) adjustment->value)
    {
      *val = adjustment->value;

      entry = gtk_object_get_user_data (GTK_OBJECT (adjustment));
      if (entry)
	{
	  sprintf (buffer, "%d", (int) adjustment->value);
	  gtk_entry_set_text (GTK_ENTRY (entry), buffer);
	}
      cwd_cache->display_page = (gint) adjustment->value - 1;

      if (delayed_updating == 0)
	delayed_updating = gtk_idle_add ((GtkFunction) thumbnail_panel_update_delayed, NULL);
    }
  display_request_redraw ();
  display_request_set_handler ();
}

static void
gtkW_ivscroll_entry_new (GtkWidget	**scroll,
			 GtkWidget	**entry,
			 GtkSignalFunc	scroll_update,
			 GtkSignalFunc	entry_update,
			 gint		*value,
			 gdouble	min,
			 gdouble	max,
			 gdouble	step,
			 gpointer	widget_entry)
{
  GtkObject	*adjustment;
  GCHAR		buffer[GTKW_ENTRY_BUFFER_SIZE];

  adjustment = gtk_adjustment_new (*value, min, max, step, step, step);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) scroll_update, value);

  *scroll = gtk_vscrollbar_new (GTK_ADJUSTMENT (adjustment));
  gtk_range_set_update_policy (GTK_RANGE (*scroll), GTK_UPDATE_CONTINUOUS);

  *entry = gtk_entry_new ();
  gtk_object_set_user_data (GTK_OBJECT (*entry), adjustment);
  gtk_object_set_user_data (GTK_OBJECT (adjustment), *entry);
  gtk_widget_set_usize (*entry, GTKW_ENTRY_WIDTH, 0);

  sprintf (buffer, "%d", *value);
  gtk_entry_set_text (GTK_ENTRY (*entry), buffer);
  gtk_signal_connect (GTK_OBJECT (*entry), "changed",
		      (GtkSignalFunc) entry_update, value);

  if (widget_entry)
    {
      gtkW_widget_table *tentry = (gtkW_widget_table *) widget_entry;

      tentry->widget = (GtkWidget *) adjustment;
      tentry->updater = gtkW_iscroll_entry_change_value;
      tentry->value = value;
    }
}

static void
gtkW_ientry_update (GtkWidget *widget,
		    gpointer   data)
{
  GtkAdjustment *adjustment;
  gint		val, new_val;

  if (! cwd_cache)
    return;

  val = cwd_cache->display_page + 1;
  new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));

  if (val != new_val)
    {
      adjustment = gtk_object_get_user_data (GTK_OBJECT (widget));

      if ((new_val >= adjustment->lower) &&
	  (new_val <= adjustment->upper))
	{
	  adjustment->value = new_val;
	  cwd_cache->display_page = (gint) new_val - 1;
	  gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
	}
    }
}

static void
gtkW_query_box (GCHAR *title, GCHAR *message, GCHAR *initial, GCHAR *data)
{
  GtkWidget *qbox;

  qbox = gtkW_query_string_box_new (title, message, initial, (gpointer) data);
  gtk_main ();
  gdk_flush ();
}

static void
gtkW_preview_force_to_update (GtkWidget *widget)
{
  /*
  GtkPreview	*preview = GTK_PREVIEW (widget);

  DEBUGBLOCK (N_("gtkW_preview_force_to_update\n"));

  gtk_preview_put (GTK_PREVIEW (widget),
		   widget->window, widget->style->black_gc,
		   (widget->allocation.width - preview->buffer_width) / 2,
		   (widget->allocation.height - preview->buffer_height) / 2,
		   0, 0,
		   widget->allocation.width, widget->allocation.height);
  */
  gdk_flush ();

  RETURN;
}

/* copied from gimp/app/interface.c */
/*
 *  A text string query box
 */
typedef struct _QueryBox QueryBox;

struct _QueryBox
{
  GtkWidget *qbox;
  GtkWidget *entry;
  gpointer data;
};

static GtkWidget *
gtkW_query_string_box_new (char        *title,
			   char        *message,
			   char        *initial,
			   gpointer     data)
{
  QueryBox  *query_box;
  GtkWidget *qbox;
  GtkWidget *vbox;
  GtkWidget *label;
  GtkWidget *entry;
  GtkWidget *button;

  query_box = g_new (QueryBox, 1);

  qbox = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (qbox), title);
  gtk_window_position (GTK_WINDOW (qbox), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (qbox), "delete_event",
		      (GtkSignalFunc) gtkW_query_box_delete_callback,
		      query_box);
  gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (qbox)->action_area),
			      gtkW_border_height);

  button = gtk_button_new_with_label (_("OK"));
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) gtkW_query_box_ok_callback,
                      query_box);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (qbox)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  button = gtk_button_new_with_label (_("Cancel"));
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) gtkW_query_box_cancel_callback,
                      query_box);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (qbox)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_border_width (GTK_CONTAINER (vbox), gtkW_border_width);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (qbox)->vbox), vbox);
  gtk_widget_show (vbox);

  label = gtk_label_new (message);
  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, FALSE, 0);
  gtk_widget_show (label);

  entry = gtk_entry_new ();
  gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
  if (initial)
    gtk_entry_set_text (GTK_ENTRY (entry), initial);
  gtk_widget_show (entry);

  query_box->qbox = qbox;
  query_box->entry = entry;
  query_box->data = data;

  gtk_widget_show (qbox);

  return qbox;
}

static gint
gtkW_query_box_delete_callback (GtkWidget *w,
				GdkEvent  *e,
				gpointer   client_data)
{
  gtkW_query_box_cancel_callback (w, client_data);
  return FALSE;
}

static void
gtkW_query_box_cancel_callback (GtkWidget *w, gpointer   client_data)
{
  QueryBox *query_box;

  query_box = (QueryBox *) client_data;

  *((GCHAR *) query_box->data) = 0;
  /*  Destroy the box  */
  gtk_widget_destroy (query_box->qbox);
  g_free (query_box);
  gtk_main_quit ();
}

static void
gtkW_query_box_ok_callback (GtkWidget *w, gpointer   client_data)
{
  QueryBox *query_box;
  char *string;

  query_box = (QueryBox *) client_data;

  /*  Get the entry data  */
  string = g_strdup (gtk_entry_get_text (GTK_ENTRY (query_box->entry)));

  /*  Call the user defined callback  */
  /* (* query_box->callback) (w, query_box->data, (gpointer) string); */
  strcpy ((GCHAR *)query_box->data, string);
  /*  Destroy the box  */
  gtk_widget_destroy (query_box->qbox);
  g_free (query_box);
  gtk_main_quit ();
}

static gint
gtkW_parse_gimprc_gint (GCHAR *name, gint default_value)
{
  GimpParam	*return_vals;
  gint		nreturn_vals;
  gint		val = default_value;

  return_vals = gimp_run_procedure ("gimp_gimprc_query",
				    &nreturn_vals,
				    GIMP_PDB_STRING, name,
				    GIMP_PDB_END);
  if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS)
    val = atoi (return_vals[1].data.d_string);
  gimp_destroy_params (return_vals, nreturn_vals);

  return val;
}

static GCHAR *
gtkW_parse_gimprc_string (GCHAR *name, GCHAR *result)
{
  GimpParam	*return_vals;
  gint		nreturn_vals;

  return_vals = gimp_run_procedure ("gimp_gimprc_query",
				    &nreturn_vals,
				    GIMP_PDB_STRING, name,
				    GIMP_PDB_END);
  if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS)
    strcpy (result, return_vals[1].data.d_string);
  else
    *result = 0;
  gimp_destroy_params (return_vals, nreturn_vals);

  return result;
}

static void
gtkW_widget_set_cursor (GtkWidget *widget, GdkCursorType cursortype)
{
  GdkCursor *cursor = NULL;

  cursor = gdk_cursor_new (cursortype);
  gdk_window_set_cursor (widget->window, cursor);
  gdk_cursor_destroy (cursor);
}
/* gtkW ends here */

static gint
save_thumbnail_as_xvpict_image (char *filename, Thumbnail *thumb)
{
  FILE		*dest;
  gint		i_width = thumb->image->width;
  gint		i_height = thumb->image->height;
  gint		npixel = i_width * i_height;

  DEBUGBLOCK (N_("save_thumbnail_as_xvpict_image (\"%s\"\n"), filename);

  if ((dest = fopen (filename, "wb")) == NULL)
    RETURN FALSE;
  fprintf (dest, "P7 332\n");
  fprintf (dest, "#XVVERSION:Version 3.10a  Rev: 12/29/94 (faked by The GIMP/GUASH%s)\n",
	   HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P)? " 2.0" : " 1.0");
  fprintf (dest, "#IMGINFO:%s\n", thumb->info + strlen (thumb->name) + 1);
  fprintf (dest, "#END_OF_COMMENTS\n");
  fprintf (dest, "%d %d 255\n", i_width, i_height);
  if (thumb->image->ch == 3)
    {
      guchar	*ch, val;
      gint rerr=0, gerr=0, berr=0;

      if (HAS_NO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
	{
	  for (ch = thumb->image->data;
	       ch < thumb->image->data + npixel * 3;
	       )
	    {
	      gint32 r, g, b;

	      r = *(ch++) + rerr;
	      r = CLAMP (r, 0, 255);
	      g = *(ch++) + gerr;
	      g = CLAMP (g, 0, 255);
	      b = *(ch++) + berr;
	      b = CLAMP (b, 0, 255);

	      fprintf (dest, "%c", ((r>>5)<<5) | ((g>>5)<<2) | (b>>6));

	      rerr = r - ( (r>>5) * 255 ) / 7;
	      gerr = g - ( (g>>5) * 255 ) / 7;
	      berr = b - ( (b>>6) * 255 ) / 3;
	    }
	}
      else
	{
	  guchar	diff_buf[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT];
	  guchar	*diff_ptr = diff_buf;
	  guchar	tmp;
	  gint		limit = npixel;

	  DPRINT (N_("save as gpict\n"));
	  for (ch = thumb->image->data;
	       ch < thumb->image->data + limit * 3;
	       )
	    {
	      val = *ch & 0xE0;
	      tmp = (*ch++ & 0x1C) << 3;
	      val |= (*ch & 0xE0) >> 3;
	      tmp |= *ch++ & 0x18;
	      val |= (*ch & 0xC0) >> 6;
	      tmp |= (*ch++ & 0x38) >> 3;

	      fprintf (dest, "%c", val);
	      /* build difference datum */
	      *diff_ptr++ = tmp;
	    }
	  /* save the rest pixels */
	  for (ch = thumb->image->data + limit * 3;
	       ch < thumb->image->data + npixel * 3;
	       )
	    {
	      /* can't combine into a expression. evaluation order problem? */
	      val = (*(ch++) & 0xE0);
	      val |= ((*(ch++) & 0xE0) >> 3);
	      val |= ((*(ch++) & 0xC0) >> 6);
	      fprintf (dest, "%c", val);
	    }
	  {
	    fwrite (diff_buf, npixel, 1, dest);
	  }
	}
    }
  else
    fwrite (thumb->image->data, i_width * i_height, 1, dest);
  fclose (dest);
  RETURN TRUE;
}

static Thumbnail *
load_xvpict_image (char *filename)
{
  FILE	*fd;
  gint	 width, height, size;
  GCHAR	w_buf[4], h_buf[4];
  GCHAR	comment[LINE_BUF_SIZE];
  guchar gpict_buffer[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT + 3];

  DEBUGBLOCK (N_("load_xvpict_image (\"%s\")\n"), filename);

  fd = fopen (filename, "rb");
  if (!fd)
    RETURN NULL;

  {
    gint	flag = TRUE;
    GCHAR	buffer[LINE_BUF_SIZE];

    while (flag)
      {
	if (fgets (buffer, LINE_BUF_SIZE -1, fd) == NULL)
	  return NULL;
	if (g_strncasecmp (buffer, "#IMGINFO", 8) == 0)
	  strcpy (comment, buffer);
	else if (g_strncasecmp (buffer, "#END_OF_COMMENTS", 16) == 0)
	  flag = FALSE;
      }
  }

  /* read the last line of comment that contains info on image size */
  if (fscanf (fd, "%s %s", w_buf, h_buf) != 2)
    {
      fclose (fd);
      RETURN NULL;
    }
  width = atoi (w_buf);
  height = atoi (h_buf);
  size = width * height;

  /* assure the position in the file!!!
     Wed Jul  8 23:21:09 1998 -> reimplement Sun Jul 19 03:17:33 1998
  */
  while (fgetc (fd) != '\n');

  {
    GCHAR	tmp[LINE_BUF_SIZE];
    GCHAR	*basename = pathname_get_basename (filename);

    if (the_loaded_data->name)
      g_free (the_loaded_data->name);
    the_loaded_data->name = g_strdup (basename);

    comment[strlen (comment) - 1] = 0; /* chop newline */

    sprintf (tmp, "%s %s", basename, comment + 9);
    if (the_loaded_data->info)
      g_free (the_loaded_data->info);
    the_loaded_data->info = g_strdup (tmp);
  }
  image_buffer_resize (the_loaded_data->image, width, height, 1);
  if (1 != fread (the_loaded_data->image->data, size, 1, fd))
    {
      fclose (fd);
      DPRINT (N_("fail to load thumbnail\n"));
      RETURN NULL;
    }

  if (HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P)
      && (1 == fread (gpict_buffer, size, 1, fd)))
    {
      guchar	dynamic_buffer[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT];
      guchar	diff[4];
      guchar	*ch = NULL;
      guchar	*data = NULL;
      gint	i;

      DPRINT (N_("guash extended thumbnail format\n"));

      g_memmove (dynamic_buffer, the_loaded_data->image->data, size);
      image_buffer_resize (the_loaded_data->image, width, height, 3);

      data = dynamic_buffer;
      ch = the_loaded_data->image->data;

      for (i = 0; i < size; i++)
	{
	  *ch++ = *data & 0xE0;
	  *ch++ = (*data & 0x1C) << 3;
	  *ch++ = (*data++ & 0x03) << 6;
	}

      data = the_loaded_data->image->data;
      diff[0] = 0;
      diff[1] = gpict_buffer[0];
      diff[2] = gpict_buffer[1];
      diff[3] = gpict_buffer[2];
      for (ch = gpict_buffer; ch < gpict_buffer + size;)
	{
	  gint i;

	  for (i = 0; i < 4; i++)
	    {
	      gint	shift = 2 * (3 - i);
	      gint	index = (*ch >> shift) & 0x03;
	      guchar	val = diff[index];

	      val = *ch++;
	      *data |= (val & 0xC0) >> 3;
	      *data = (guchar) (*data * 0xFF / 0xFC);
	      data++;
	      *data |= val & 0x18;
	      *data = (guchar) (*data * 0xFF / 0xF8);
	      data++;
	      *data |= (val & 0x07) << 3;
	      *data = (guchar) (*data * 0xFF / 0xFC);
	      data++;
	    }
	}
    }
  fclose (fd);

  RETURN the_loaded_data;
}

/*
 * Local variables:
 * compile-command: "gimptool --install guash.c"
 * End:
 */
/* guash.c ends here */
