#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>

#include "../include/string.h"
#include "../include/disk.h"

#include "guiutils.h"
#include "cdialog.h"

#include "cfg.h"
#include "edv_types.h"
#include "edv_obj.h"
#include "edv_status_bar.h"
#include "archiver.h"
#include "archiver_contents_list.h"
#include "browser.h"
#include "browser_dir_tree.h"
#include "browser_contents_list.h"
#include "recbin_contents_list.h"
#include "imbr.h"
#include "imbr_tlist.h"
#include "find_win.h"
#include "find_win_cb.h"
#include "endeavour2.h"
#include "edv_find.h"
#include "edv_open.h"
#include "edv_op.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"


void EDVFindWinListItemDestroyCB(gpointer data);

gint EDVFindWinDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
void EDVFindWinDestroyCB(GtkObject *object, gpointer data);

gint EDVFindWinCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
gint EDVFindWinButtonCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);

void EDVFindWinComboActivateCB(GtkWidget *widget, gpointer data);

gint EDVFindWinFindProgressCB(
	const gchar *path, gfloat progress, gpointer data
);

void EDVFindWinFindMatchedExcerptCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
);
void EDVFindWinFindMatchedCB(
	const gchar *path, const struct stat *lstat_buf,
	gpointer data
);

void EDVFindWinClickColumnCB(
	GtkCList *clist, gint column, gpointer data
);
void EDVFindWinSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
void EDVFindWinUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);

void EDVFindWinSearchCB(GtkWidget *widget, gpointer data);
void EDVFindWinStopCB(GtkWidget *widget, gpointer data);
void EDVFindWinClearCB(GtkWidget *widget, gpointer data);
void EDVFindWinCloseCB(GtkWidget *widget, gpointer data);
void EDVFindWinOpenCB(GtkWidget *widget, gpointer data);
void EDVFindWinOpenWithCB(GtkWidget *widget, gpointer data);
void EDVFindWinGotoCB(GtkWidget *widget, gpointer data);

void EDVFindWinWriteProtectChangedCB(
	edv_find_win_struct *fw, gboolean state
);
void EDVFindWinReconfiguredNotifyCB(edv_find_win_struct *fw);


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Find window results GtkCList item "destroy" signal callback.
 */
void EDVFindWinListItemDestroyCB(gpointer data)
{
	EDVObjectDelete(EDV_OBJECT(data));
}

/*
 *	Find window GtkWindow "delete_event" signal callback.
 */
gint EDVFindWinDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	EDVFindWinCloseCB(widget, data);
	return(TRUE);
}

/*
 *	Find window "destroy" signal callback.
 */
void EDVFindWinDestroyCB(GtkObject *object, gpointer data)
{
	return;
}

/*
 *	"enter_notify_event" or "leave_notify_event" signal callback.
 */
gint EDVFindWinCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	const gchar *mesg = NULL;
	gint etype;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if((widget == NULL) || (crossing == NULL) || (fw == NULL))
	    return(FALSE);

	etype = crossing->type;

	if(etype == GDK_ENTER_NOTIFY)
	{
	    if(widget == fw->case_sensitive_check)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Especifica si los iguales de cuerda no deben ser el casos sensibles ni"
#elif defined(PROG_LANGUAGE_FRENCH)
"Spécifie si les allumettes de ficelle doivent être des cas sensibles ou pas"
#elif defined(PROG_LANGUAGE_GERMAN)
"Gibt an, wenn schnur gegenstücke fall empfindlich oder nicht sein sollen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Non specifica se i fiammiferi di cordicella dovrebbero essere dei casi sensibili o"
#elif defined(PROG_LANGUAGE_DUTCH)
"Specificeert indien koord stellen geval gevoelig of niet zouden moeten zijn"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Especifica se partidas de barbante devem ser caso sensível ou não"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Spesifiserer om snorkamper er tilfelle sensitiv eller ikke"
#else
"Specifies if string matches should be case sensitive or not"
#endif
		;
	    else if(widget == fw->recursive_check)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Especifica si el proceso de la búsqueda debe recurse en subdirectorios"
#elif defined(PROG_LANGUAGE_FRENCH)
"Spécifie si le procédé de recherche devrait recurse dans subdirectories"
#elif defined(PROG_LANGUAGE_GERMAN)
"Gibt an, wenn das suche verfahren recurse in unterverzeichnisse soll"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Specifica se il processo di ricerca dovrebbe il recurse nelle directory secondarie"
#elif defined(PROG_LANGUAGE_DUTCH)
"Specificeert indien het zoektocht proces recurse in subdirectories zal"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Especifica se o processo de busca devia recurse em subdirectories"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Spesifiserer om letingssom prosessen skal recurse inn i subdirectories"
#else
"Specifies if the search process should recurse into subdirectories"
#endif
		;
	    else if(widget == fw->search_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Empiece la búsqueda"
#elif defined(PROG_LANGUAGE_FRENCH)
"Commencer la recherche"
#elif defined(PROG_LANGUAGE_GERMAN)
"Fangen sie die suche an"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cominciare la ricerca"
#elif defined(PROG_LANGUAGE_DUTCH)
"Begin de zoektocht"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Comece a busca"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Start letingen"
#else
"Start the search"
#endif
		;
	    else if(widget == fw->stop_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Pare el procedimiento actual del hallazgo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Arrêter la procédure actuelle de découverte"
#elif defined(PROG_LANGUAGE_GERMAN)
"Halten sie das jetzige fund verfahren auf"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Fermare la corrente trova la procedura"
#elif defined(PROG_LANGUAGE_DUTCH)
"Stop de huidig vondst procedure"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Pare a corrente achar procedimento"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stans den nåværendee funnprosedyre"
#else
"Stop the current find procedure"
#endif
		;
	    else if(widget == fw->clear_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"La cuerda clara de la búsqueda y la lista de resultados"
#elif defined(PROG_LANGUAGE_FRENCH)
"La ficelle clairs de recherche et la liste de résultats"
#elif defined(PROG_LANGUAGE_GERMAN)
"Klare suche schnur und ergebnisse führen auf"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'elenco di cordicella di ricerca e risultati chiaro"
#elif defined(PROG_LANGUAGE_DUTCH)
"Helder zoektocht koord en resultaten sommen op"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Barbante clara de busca e lista de resultados"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Klar letingssnor og resultater lister opp"
#else
"Clear search string and results list"
#endif
		;
	    else if(widget == fw->close_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Cierre esta ventana"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fermer cette fenêtre"
#elif defined(PROG_LANGUAGE_GERMAN)
"Schließen sie dieses fenster"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Vicino questa finestra"
#elif defined(PROG_LANGUAGE_DUTCH)
"Sluit deze raam"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Próximo esta janela"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stenge dette vinduet"
#else
"Close this window"
#endif
		;
	    else if(widget == fw->open_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Abrir archivo escogido"
#elif defined(PROG_LANGUAGE_FRENCH)
"Ouvrir l'objet choisi"
#elif defined(PROG_LANGUAGE_GERMAN)
"Offen ausgewählten objekt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Aperto scelto oggetto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Open geselecteerd voorwerp"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Objeto selecionado aberto"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpn valgt ut objekt"
#else
"Open selected object"
#endif
		;
	    else if(widget == fw->open_with_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Abrir archivo escogido utilizando un método específico"
#elif defined(PROG_LANGUAGE_FRENCH)
"Ouvrir l'objet choisi utilisant une méthode spécifique"
#elif defined(PROG_LANGUAGE_GERMAN)
"Offen ausgewählten objekt, eine spezifische methode zu benutzen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Aperto scelto oggetto usando uno specifico metodo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Open geselecteerd voorwerp een specifieke methode te gebruiken"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Objeto selecionado aberto usando um método específico"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpn valgt ut objekt bruke en spesifikk metode"
#else
"Open selected object using a specific method"
#endif
		;
	    else if(widget == fw->goto_btn)
		mesg =
#if defined(PROG_LANGUAGE_SPANISH)
"Vaya a escogido se opone"
#elif defined(PROG_LANGUAGE_FRENCH)
"Aller à l'objet choisi"
#elif defined(PROG_LANGUAGE_GERMAN)
"Gehen sie zu ausgewähltem objekt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Andare all'oggetto scelto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ga te geselecteerd voorwerp"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Vá a objeto selecionado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Dra til valgt ut objekt"
#else
"Go to selected object"
#endif
		;
	}
	EDVStatusBarMessage(fw->status_bar, mesg, FALSE);

	return(FALSE);
}

/*
 *	"button_press_event" or "button_release_event" signal callback.
 */
gint EDVFindWinButtonCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	gboolean is_press;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if((widget == NULL) || (button == NULL) || (fw == NULL))
	    return(status);

	if(fw->processing)
	    return(status);

	core = fw->core;
	if(core == NULL)
	    return(status);

	cfg_list = core->cfg_list;

	etype = (gint)button->type;
	is_press = (etype == GDK_BUTTON_PRESS) ? TRUE : FALSE;

	/* Check which ctree this signal is for */
	if(widget == fw->results_clist)
	{
	    gint row, column;
	    gint rows_selected = 0, selected_row = -1;
	    GList *glist;
	    GtkCList *clist = GTK_CLIST(widget);


	    /* Find row and column based on given coordinates */
	    if(!gtk_clist_get_selection_info(
		clist, (gint)button->x, (gint)button->y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Get number of selected rows and highest selected row */
	    glist = clist->selection;
	    while(glist != NULL)
	    {
		rows_selected++;
		selected_row = (gint)glist->data;
		glist = g_list_next(glist);
	    }

	    /* If button press then grab focus as needed */
	    if(is_press && !GTK_WIDGET_HAS_FOCUS(widget))
		gtk_widget_grab_focus(widget);

	    /* Handle by button number */
	    switch(button->button)
	    {
	      case 3:
		if(is_press)
		{
		    GtkMenu *menu = (GtkMenu *)fw->results_clist_menu;

		    /* Select item before mapping menu? */
		    if(EDV_GET_B(EDV_CFG_PARM_RIGHT_CLICK_MENU_SELECTS) &&
		       (row >= 0) && (row < clist->rows)
		    )
		    {
			/* Select the row that the button was pressed
			 * over
			 *
			 * if no key modifiers are held then this will
			 * also unselect all previously selected rows
			 */
			gtk_clist_freeze(clist);
			if(!(button->state & GDK_CONTROL_MASK) &&
			   !(button->state & GDK_SHIFT_MASK)
			)
			    gtk_clist_unselect_all(clist);
			clist->focus_row = row;
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }

		    /* Update all menus and map right click menu */
		    EDVFindWinUpdateMenus(fw);
		    if(menu != NULL)
			gtk_menu_popup(
			    menu, NULL, NULL,
			    NULL, NULL,
			    button->button, button->time
			);
		}
		status = TRUE;
		break;

	      case 2:
		if(is_press)
		{
#if 0
		    if((row >= 0) && (row < clist->rows))
			EDVFindWinContentsDoFPromptRename(
			    fw, row, column
			);
#endif
		}
		status = TRUE;
		break;

	      case 1:
		/* Double click? */
		if(etype == GDK_2BUTTON_PRESS)
		{
		    if((row >= 0) && (row < clist->rows))
		    {
			EDVFindWinOpenCB(NULL, fw);
			status = TRUE;
		    }
		}
		break;
	    }
	}

	return(status);
}






/*
 *	Search or Location GtkCombo "activate" signal callback.
 */
void EDVFindWinComboActivateCB(GtkWidget *widget, gpointer data)
{
	EDVFindWinSearchCB(widget, data);
}


/*
 *	Find procedure progress callback.
 *
 *	Returns the number of stop counts.
 */
gint EDVFindWinFindProgressCB(
	const gchar *path, gfloat progress, gpointer data
)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return(0);

	if(fw->stop_count > 0)
	    return(fw->stop_count);

	EDVStatusBarMessage(fw->status_bar, path, FALSE);
	EDVStatusBarProgress(fw->status_bar, progress, TRUE);

	return(0);
}

/*
 *	Find procedure matched excerpt callback.
 */
void EDVFindWinFindMatchedExcerptCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	EDVFindWinListAppend(fw, path, lstat_buf, excerpt, line_index);
}

/*
 *	Find procedure matched callback.
 */
void EDVFindWinFindMatchedCB(
	const gchar *path, const struct stat *lstat_buf, gpointer data
)
{
	EDVFindWinFindMatchedExcerptCB(path, lstat_buf, NULL, -1, data);
}


/*
 *	GtkCList "click_column" signal callback.
 */
void EDVFindWinClickColumnCB(
	GtkCList *clist, gint column, gpointer data
)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if((clist == NULL) || (fw == NULL))
	    return;

	if(fw->processing)
	    return;

}

/*
 *	GtkCList "select_row" signal callback.
 */
void EDVFindWinSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if((clist == NULL) || (fw == NULL))
	    return;

	if(fw->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == fw->results_clist)
	{
	    gint total_selected = (clist->selection != NULL) ?
		g_list_length(clist->selection) : 0;
	    const edv_object_struct *obj = EDV_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );

#if 0
	    /* Update selected row */
	    fw->results_clist_selected_row = row;

	    /* Update DND icon for results clist */
	    EDVFindWinContentsDNDSetIcon(fw, row, column);
#endif
	    if(obj != NULL)
	    {
		/* Update status bar message */
		if(!STRISEMPTY(obj->full_path))
		{
		    gchar *buf;
		    const gchar	*name,
				*type_str = "Object";
		    gchar size_str[80];

		    /* Get name from full path, since the name of the
		     * object may be a string specifying a special
		     * notation depending on the role
		     */
		    name = g_basename(obj->full_path);

		    /* Get the object's type & size strings */
		    *size_str = '\0';
		    switch(obj->type)
		    {
		      case EDV_OBJECT_TYPE_UNKNOWN:
			type_str = "Object";
			break;
		      case EDV_OBJECT_TYPE_FILE:
			type_str = "File";
			g_snprintf(
			    size_str, sizeof(size_str),
			    " (%ld byte%s)",
			    obj->size,
			    (obj->size == 1) ? "" : "s"
			);
			break;
		      case EDV_OBJECT_TYPE_DIRECTORY:
			type_str = "Directory";
			break;
		      case EDV_OBJECT_TYPE_LINK:
			type_str = "Link";
			break;
		      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
			type_str = "Block device";
			break;
		      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
			type_str = "Character device";
			break;
		      case EDV_OBJECT_TYPE_FIFO:
			type_str = "FIFO Pipe";
			break;
		      case EDV_OBJECT_TYPE_SOCKET:
			type_str = "Socket";
			break;
		      case EDV_OBJECT_TYPE_ERROR:
			type_str = "Error";
			break;
		    }

		    /* Set status bar message */
		    if(total_selected > 1)
			buf = g_strdup_printf(
			    "%i objects selected",
			    total_selected
			);
		    else if(!strcmp(name, ".."))
			buf = g_strdup_printf(
			    "Parent directory selected"
			);
		    else
			buf = g_strdup_printf(
			    "%s \"%s\" selected%s",
			    type_str, name, size_str
			);
		    EDVStatusBarMessage(
			fw->status_bar, buf, FALSE
		    );
		    g_free(buf);
		}
		else
		{
		    EDVStatusBarMessage(
			fw->status_bar,
			"Object with no name selected",
			FALSE
		    );
		}
	    }

	    /* Scroll if selected row is not visible */
	    if(gtk_clist_row_is_visible(clist, row) !=
		GTK_VISIBILITY_FULL
	    )
		gtk_clist_moveto(
		    clist,
		    row, -1,	/* Row, column */
		    0.5f, 0.0f	/* Row, column */
		);

/*	    EDVFindWinSetTitle(fw); */
	    EDVFindWinUpdateMenus(fw);
	}
}

/*
 *	GtkCList "unselect_row" signal callback.
 */
void EDVFindWinUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if((clist == NULL) || (fw == NULL))
	    return;

	if(fw->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == fw->results_clist)
	{


/*	    EDVFindWinSetTitle(fw); */
	    EDVFindWinUpdateMenus(fw);
	}
}


/*
 *	Search callback.
 *
 *	Starts the find procedure.
 */
void EDVFindWinSearchCB(GtkWidget *widget, gpointer data)
{
	gboolean	case_sensitive = FALSE,
			recursive = FALSE;
	gchar		*location,
			*parent_path,
			*search_string;
	gint		total_matches = 0;
	edv_findwin_operation operation;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	core = fw->core;
	if(core == NULL)
	    return;

	fw->processing = TRUE;

	cfg_list = core->cfg_list;


	/* Get search parameters */

	/* Find operation */
	operation = EDVFindWinCurrentOperation(fw);

	/* Search string */
	search_string = STRDUP(EDVFindWinCurrentSearch(fw));
	/* Are there no wild cards in search string? */
	if((strchr(search_string, '*') == NULL) &&
	   (strchr(search_string, '?') == NULL)
	)
	{
	    /* If there are no wild cards in the search string then
	     * we need to add them on for certain find operations that
	     * require wildcards be used
	     */
	    if(operation == EDV_FIND_WIN_FIND_OBJECT_NAME)
	    {
		/* Tack on two '*' characters to the beginning and the
		 * end of the search string
		 */
		gchar *s = g_strdup_printf("*%s*", search_string);
		g_free(search_string);
		search_string = s;
	    }
	}
	/* Record search string in the find window's history */
	EDVFindWinSetSearch(fw, search_string, TRUE);

	/* Get Starting location depending on the role */
	switch(fw->role)
	{
	  case EDV_FIND_WIN_ROLE_DISK_OBJECT:
	  case EDV_FIND_WIN_ROLE_ARCHIVE_OBJECT:
	    /* Get current location as the starting location */
	    location = EDVEvaluatePath(
		NULL, EDVFindWinCurrentLocation(fw)
	    );
	    /* Record starting location history */
	    EDVFindWinSetLocation(fw, location, TRUE);
	    break;

	  case EDV_FIND_WIN_ROLE_RECYCLED_OBJECT:
	    /* Get recycled objects index file as the starting location */
	    location = EDVEvaluatePath(
		NULL, EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX)
	    );
	    /* Record starting location history */
	    parent_path = g_dirname(location);
	    EDVFindWinSetLocation(fw, parent_path, TRUE);
	    g_free(parent_path);
	    break;

	  default:
	    location = NULL;
	    break;
	}

	/* Case sensitive */
	case_sensitive = GTK_TOGGLE_BUTTON_GET_ACTIVE(
	    fw->case_sensitive_check
	);

	/* Recursive */
	recursive = GTK_TOGGLE_BUTTON_GET_ACTIVE(
	    fw->recursive_check
	);


	/* Begin search */

	fw->stop_count = 0;
	EDVFindWinSetBusy(fw, TRUE);
	EDVFindWinListClear(fw);
	EDVFindWinListResetColumns(fw, operation);
	EDVFindWinUpdateMenus(fw);

	/* Perform find procedure by find operation code */
	switch(operation)
	{
	  case EDV_FIND_WIN_FIND_OBJECT_NAME:
	    switch(fw->role)
	    {
	      case EDV_FIND_WIN_ROLE_DISK_OBJECT:
		total_matches = EDVFindObjectByName(
		    location, search_string,
		    recursive,		/* Recursive */
		    FALSE,		/* Do not follow links */
		    case_sensitive,	/* Case sensitive */
		    fw,
		    EDVFindWinFindProgressCB,
		    EDVFindWinFindMatchedCB
		);
		break;

	      case EDV_FIND_WIN_ROLE_RECYCLED_OBJECT:
		total_matches = EDVFindRecycledObjectByName(
		    location,		/* Recycled objects index file */
		    search_string,
		    case_sensitive,	/* Case sensitive */
		    fw,
		    EDVFindWinFindProgressCB,
		    EDVFindWinFindMatchedCB
		);
		break;

	      case EDV_FINDBAR_ROLE_ARCHIVE_OBJECT:
		total_matches = EDVFindArchiveObjectByName(
		    core,
		    location,		/* Archive */
		    search_string,
		    case_sensitive,	/* Case sensitive */
		    fw,
		    EDVFindWinFindProgressCB,
		    EDVFindWinFindMatchedCB
		);
		break;
	    }
	    break;

	  case EDV_FIND_WIN_FIND_OBJECT_CONTENT:
	    switch(fw->role)
	    {
	      case EDV_FIND_WIN_ROLE_DISK_OBJECT:
		total_matches = EDVFindObjectByContent(
		    location, search_string,
		    recursive,		/* Recursive */
		    FALSE,		/* Do not follow links */
		    case_sensitive,	/* Case sensitive */
		    fw,
		    EDVFindWinFindProgressCB,
		    EDVFindWinFindMatchedExcerptCB
		);
		break;

	      case EDV_FIND_WIN_ROLE_RECYCLED_OBJECT:
		total_matches = EDVFindRecycledObjectByContent(
		    location,		/* Recycled objects index file */
		    search_string,
		    case_sensitive,	/* Case sensitive */
		    fw,
		    EDVFindWinFindProgressCB,
		    EDVFindWinFindMatchedExcerptCB
		);
		break;

	      case EDV_FINDBAR_ROLE_ARCHIVE_OBJECT:
		/* Finding archive object by content is not supported */
		total_matches = 0;
		break;
	    }
	    break;
	}



	/* Begin tallying results */
	EDVStatusBarProgress(fw->status_bar, 0.0f, FALSE);
	if(total_matches > 0)
	{
	    gchar *buf = g_strdup_printf(
		"Matched %i object%s%s",
		total_matches,
		(total_matches == 1) ? "" : "s",
		(fw->stop_count > 0) ?
		    " (search interrupted)" : ""
	    );
	    EDVStatusBarMessage(fw->status_bar, buf, FALSE);
	    g_free(buf);
	}
	else
	{
	    EDVStatusBarMessage(
		fw->status_bar,
		(fw->stop_count > 0) ?
"No objects found (search interrupted)" :
"No objects found",
		FALSE
	    );
	}


	/* Set values on find window reflecting end of search */
	fw->stop_count = 0;
	EDVFindWinSetBusy(fw, FALSE);
	fw->processing = FALSE;
	EDVFindWinUpdateMenus(fw);


	g_free(location);
	g_free(search_string);
}

/*
 *	Stop callback.
 */
void EDVFindWinStopCB(GtkWidget *widget, gpointer data)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	/* Skip processing check, this callback is allowed to continue
	 * during processing
	 */

	fw->stop_count++;
}

/*
 *	Clear callback.
 */
void EDVFindWinClearCB(GtkWidget *widget, gpointer data)
{
	GtkCombo *combo;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	combo = (GtkCombo *)fw->search_combo;
	if(combo != NULL)
	    gtk_entry_set_text(GTK_ENTRY(combo->entry), "");

	EDVFindWinListClear(fw);
	EDVFindWinUpdateMenus(fw);
}

/*
 *	Close callback.
 */
void EDVFindWinCloseCB(GtkWidget *widget, gpointer data)
{
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	EDVFindWinSyncConfiguration(fw);
	EDVFindWinUnmap(fw);
}

/*
 *	Open callback.
 */
void EDVFindWinOpenCB(GtkWidget *widget, gpointer data)
{
	gint row;
	GtkWidget *toplevel;
	GtkCList *clist;
	const edv_object_struct *obj;
	edv_core_struct *core;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	toplevel = fw->toplevel;
	clist = (GtkCList *)fw->results_clist;
	core = fw->core;
	if((clist == NULL) || (core == NULL))
	    return;

	/* Unsupported role for opening object? */
	if(fw->role != EDV_FIND_WIN_ROLE_DISK_OBJECT)
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageWarning(
#if defined(PROG_LANGUAGE_SPANISH)
"Apertura Fallida",
"El objeto no es un objeto normal de disco, sólo objetos normales\n\
de disco se pueden abrir.",
#elif defined(PROG_LANGUAGE_FRENCH)
"Ouvrir Echoué",
"L'objet n'est pas un objet de disque normal, les objets de\n\
disque seulement normaux peuvent être ouverts.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Offen Versagt",
"Das objekt ist kein normales scheibe objekt, nur normale\n\
scheibe, die objekte geöffnet werden können",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Aperto Fallito",
"L'oggetto non è un oggetto di dischetto normale, gli oggetti\n\
di dischetto soltanto normali possono essere aperti.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Open Verzuimde",
"Het voorwerp kunnen geen normaal schijf voorwerp, enige\n\
normale schijf voorwerpen geopend worden is.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Aberto Fracassado",
"O objeto não é um objeto normal de disco, objetos só normais de\n\
disco podem ser abertos.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpn Sviktet",
"Objektet er ikke et normalt skiveobjekt, bare normal\n\
skiveobjekt åpnet.",
#else
"Open Failed",
"The object is not a normal disk object, only normal disk objects\n\
can be opened.",
#endif
		NULL,
		toplevel
	    );
	    return;
	}

	/* Get last selected row and object */
	row = EDVCListGetSelectedLast(clist, NULL);
	obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));

	if((obj != NULL) ? !STRISEMPTY(obj->full_path) : FALSE)
	{
	    const gchar *full_path = obj->full_path;

	    /* Is it a directory? */
	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
/* TODO  */
	    }
	    else
	    {
		gchar	*stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
		GList *paths_list = NULL;

		paths_list = g_list_append(
		    paths_list,
		    STRDUP(full_path)
		);

		EDVFindWinSetBusy(fw, TRUE);
		EDVOpen(
		    core,
		    paths_list,			/* Paths List */
		    NULL,			/* Command Name */
		    toplevel,			/* Toplevel */
		    TRUE,			/* Verbose */
		    &stdout_path_rtn,
		    &stderr_path_rtn
		);
		EDVFindWinSetBusy(fw, FALSE);

		g_list_foreach(paths_list, (GFunc)g_free, NULL);
		g_list_free(paths_list);

		g_free(stdout_path_rtn);
		g_free(stderr_path_rtn);
	    }
	}
}

/*
 *	Open with callback.
 */
void EDVFindWinOpenWithCB(GtkWidget *widget, gpointer data)
{
	gint row;
	GtkWidget *toplevel;
	GtkCList *clist;
	const edv_object_struct *obj;
	edv_core_struct *core;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	toplevel = fw->toplevel;
	clist = (GtkCList *)fw->results_clist;
	core = fw->core;
	if((clist == NULL) || (core == NULL))
	    return;

	/* Unsupported role for opening object? */
	if(fw->role != EDV_FIND_WIN_ROLE_DISK_OBJECT)
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageWarning(
#if defined(PROG_LANGUAGE_SPANISH)
"Apertura Fallida",
"El objeto no es un objeto normal de disco, sólo objetos normales\n\
de disco se pueden abrir.",
#elif defined(PROG_LANGUAGE_FRENCH)
"Ouvrir Echoué",
"L'objet n'est pas un objet de disque normal, les objets de\n\
disque seulement normaux peuvent être ouverts.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Offen Versagt",
"Das objekt ist kein normales scheibe objekt, nur normale\n\
scheibe, die objekte geöffnet werden können",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Aperto Fallito",
"L'oggetto non è un oggetto di dischetto normale, gli oggetti\n\
di dischetto soltanto normali possono essere aperti.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Open Verzuimde",
"Het voorwerp kunnen geen normaal schijf voorwerp, enige\n\
normale schijf voorwerpen geopend worden is.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Aberto Fracassado",
"O objeto não é um objeto normal de disco, objetos só normais de\n\
disco podem ser abertos.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpn Sviktet",
"Objektet er ikke et normalt skiveobjekt, bare normal\n\
skiveobjekt åpnet.",
#else
"Open Failed",
"The object is not a normal disk object, only normal disk objects\n\
can be opened.",
#endif
		NULL,
		toplevel
	    );
	    return;
	}

	/* Get last selected row and object */
	row = EDVCListGetSelectedLast(clist, NULL);
	obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));

	if((obj != NULL) ? !STRISEMPTY(obj->full_path) : FALSE)
	{
	    gchar       *stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
	    GList *paths_list = NULL;

	    paths_list = g_list_append(
		paths_list,
		STRDUP(obj->full_path)
	    );

	    EDVOpenWith(
		core,
		paths_list,		/* Paths List */
		NULL,			/* Command Name */
		toplevel,		/* Toplevel */
		TRUE,			/* Verbose */
		&stdout_path_rtn,
		&stderr_path_rtn
	    );

	    g_list_foreach(paths_list, (GFunc)g_free, NULL);
	    g_list_free(paths_list);

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}
}


/*
 *	Goto callback.
 */
void EDVFindWinGotoCB(GtkWidget *widget, gpointer data)
{
	gint row;
	GtkCList *clist;
	const edv_object_struct *obj;
	edv_core_struct *core;
	gchar *parent_dir, *full_path;
	edv_find_win_struct *fw = EDV_FIND_WIN(data);
	if(fw == NULL)
	    return;

	if(fw->processing)
	    return;

	clist = (GtkCList *)fw->results_clist;
	core = fw->core;
	if((clist == NULL) || (core == NULL))
	    return;

	/* Unsupported role for going to object? */
	if(fw->role != EDV_FIND_WIN_ROLE_DISK_OBJECT)
	    return;

	/* Get last selected row and object */
	row = EDVCListGetSelectedLast(clist, NULL);
	obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));

	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	/* Get copy of full path and parent directory */
	full_path = EDVEvaluatePath(NULL, obj->full_path);
	parent_dir = g_dirname(full_path);


	/* Check which window `go to' by checking if its index set on
	 * the find window structure is non-negative
	 */
	/* File browser? */
	if(fw->browser_num > -1)
	{
	    const gint browser_num = fw->browser_num;
	    edv_browser_struct *browser;

	    if((browser_num >= 0) && (browser_num < core->total_browsers))
		browser = core->browser[browser_num];
	    else
		browser = NULL;

	    if(browser != NULL)
	    {
		const gchar *cur_path = EDVBrowserCurrentLocation(browser);
		GtkWidget *toplevel = browser->toplevel;
		GtkCList *clist = (GtkCList *)browser->contents_clist;

		EDVBrowserSetBusy(browser, TRUE);
		GUIBlockInput(toplevel, TRUE);

		/* Is the parent directory within the Directory Tree? */
		if(EDVBrowserIsPathFromDirTreeOrigin(browser, parent_dir))
		{
		    /* Select the parent directory only if its not the
		     * current directory
		     */
		    if((cur_path != NULL) ? strcmp(cur_path, parent_dir) : TRUE)
			EDVBrowserDirTreeSelectPath(browser, parent_dir);
		}
		else
		{
		    /* Set the Directory Tree origin as the parent
		     * directory and select the parent directory
		     */
		    EDVBrowserDirTreeSetOriginPath(browser, parent_dir);
		}


		/* The contents listing on the browser should be updated
		 * if the parent directory was selected successfully
		 *
		 * Now find the object on the content's clist who's path
		 * matches the path of the object
		 */
		if(clist != NULL)
		{
		    const gint row = EDVBrowserContentsFindRowByPath(
			browser, full_path
		    );
		    if((row >= 0) && (row < clist->rows))
		    {
			gtk_clist_freeze(clist);
			gtk_clist_unselect_all(clist);
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }
		}

		GUIBlockInput(toplevel, FALSE);
		EDVBrowserSetBusy(browser, FALSE);
	    }
	}
	/* Image browser? */
	else if(fw->imbr_num > -1)
	{
	    const gint imbr_num = fw->imbr_num;
	    edv_imbr_struct *imbr;

	    if((imbr_num >= 0) && (imbr_num < core->total_imbrs))
		imbr = core->imbr[imbr_num];
	    else
		imbr = NULL;

	    if(imbr != NULL)
	    {
		const gchar *cur_path = EDVImbrCurrentLocation(imbr);
		tlist_struct *tlist = imbr->tlist;

		/* Select the parent directory only if its different
		 * than the current directory
		 */
		if((cur_path != NULL) ? strcmp(cur_path, parent_dir) : TRUE)
		    EDVImbrSelectPath(imbr, parent_dir);

		/* The thumbs list should now contain a list of the
		 * contents of the parent_dir
		 *
		 * Now we look for a particular thumb that matches the
		 * path of the object
		 */
		if(tlist != NULL)
		{
		    const gint thumb_num = EDVImbrTListFindThumbByPath(
			imbr, full_path
		    );
		    TListFreeze(tlist);
		    TListUnselectAll(tlist);
		    TListSelectThumb(tlist, thumb_num);
		    TListThaw(tlist);
		}
	    }
	}
	/* Recycle bin */
	else if(fw->recbin_num > -1)
	{
	    edv_recbin_struct *recbin = core->recbin;
	    if((recbin != NULL) && (obj->name != NULL))
	    {
		GtkCList *clist = (GtkCList *)recbin->contents_clist;
		if(clist != NULL)
		{
		    /* The object's name is a string specifying the
		     * recycled object index number
		     */
		    const gint row = EDVRecBinContentsFindRowByIndex(
			recbin, (guint)ATOI(obj->name)
		    );
		    if((row >= 0) && (row < clist->rows))
		    {
			gtk_clist_freeze(clist);
			gtk_clist_unselect_all(clist);
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }
		}
	    }
	}
	/* Archiver */
	else if(fw->archiver_num > -1)
	{
	    const gint archiver_num = fw->archiver_num;
	    edv_archiver_struct *archiver;

	    if((archiver_num >= 0) && (archiver_num < core->total_archivers))
		archiver = core->archiver[archiver_num];
	    else
		archiver = NULL;

	    if(archiver != NULL)
	    {
		GtkCList *clist = (GtkCList *)archiver->contents_clist;
		if(clist != NULL)
		{
		    const gint row = EDVArchiverContentsFindRowByPath(
			archiver, full_path
		    );
		    if((row >= 0) && (row < clist->rows))
		    {
			gtk_clist_freeze(clist);
			gtk_clist_unselect_all(clist);
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }
		}
	    }
	}
	/* All else create a new file browser */
	else
	{
	    const gint browser_num = EDVNewBrowser(core, parent_dir);
	    edv_browser_struct *browser = (browser_num > -1) ?
		core->browser[browser_num] : NULL;

	    fw->browser_num = browser_num;

	    if(browser != NULL)
	    {
		/* The contents listing on the browser should be updated
		 * if the parent directory was selected successfully
		 *
		 * Now find the object on the content's clist who's path
		 * matches the path of the object
		 */
		GtkCList *clist = (GtkCList *)browser->contents_clist;
		if(clist != NULL)
		{
		    const gint row = EDVBrowserContentsFindRowByPath(
			browser, full_path
		    );
		    if((row >= 0) && (row < clist->rows))
		    {
			gtk_clist_freeze(clist);
			gtk_clist_unselect_all(clist);
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }
		}
	    }
	}

	g_free(parent_dir);
	g_free(full_path);
}


/*
 *	Find Window write protect changed signal callback.
 */
void EDVFindWinWriteProtectChangedCB(
	edv_find_win_struct *fw, gboolean state
)
{
	if(fw == NULL)
	    return;

	EDVFindWinUpdateMenus(fw);	
}

/*
 *	Find Window reconfigured notify signal callback.
 */
void EDVFindWinReconfiguredNotifyCB(edv_find_win_struct *fw)
{
	GtkRcStyle *standard_rcstyle, *lists_rcstyle;
	GtkWidget *w;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(fw == NULL)
	    return;

	core = fw->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;
	standard_rcstyle = core->standard_rcstyle;
	lists_rcstyle = core->lists_rcstyle;


	/* Begin updating */

	/* Update RC styles */
	w = fw->toplevel;
	if((w != NULL) && (standard_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, standard_rcstyle);
	w = fw->results_clist;
	if((w != NULL) && (lists_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, lists_rcstyle);
	w = fw->results_clist_menu;
	if((w != NULL) && (standard_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, standard_rcstyle);



	EDVFindWinUpdateMenus(fw);
}
