#include <stdlib.h>
#include <ctype.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

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

#include "editor.h"
#include "viewer.h"
#include "viewercb.h"
#include "viewerfio.h"
#include "pref.h"
#include "prefop.h"
#include "manedit.h"
#include "config.h"


void ViewerIndexBranchDestroyCB(gpointer data);

void ViewerDestroyCB(GtkObject *object, gpointer data);
gint ViewerCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void ViewerCloseMCB(GtkWidget *widget, gpointer data);
void ViewerCloseAllCB(GtkWidget *widget, gpointer data);

void ViewerSwitchPageCB(
	GtkNotebook *notebook, GtkNotebookPage *page, guint page_num,
	gpointer data
);
void ViewerPageToggleCB(GtkWidget *widget, gpointer data);
gint ViewerPageChangedTOCB(gpointer data);

gint ViewerViewKeyEventCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
);

void ViewerIndexBranchExpandCB(GtkWidget *widget, gpointer data);

void ViewerOpenCB(GtkWidget *widget, gpointer data);
void ViewerClearCB(GtkWidget *widget, gpointer data);
void ViewerStopCB(GtkWidget *widget, gpointer data);
void ViewerIndexGotoParentCB(GtkWidget *widget, gpointer data);
void ViewerGotoCB(GtkWidget *widget, gpointer data);
void ViewerIndexRefreshCB(GtkWidget *widget, gpointer data);
void ViewerPreferencesCB(GtkWidget *widget, gpointer data);

gint ViewerMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data);

void ViewerManPageActivateCB(GtkWidget *widget, void *data);
static void ViewerDoFindCB(
	viewer_struct *v, GtkCombo *find_combo, gboolean case_sensitive
);
void ViewerFindActivateCB(GtkWidget *widget, void *data);

static gboolean ViewerFindInPagesDoGrep(
	const gchar *path, gchar *expression, gboolean case_sensitive
);
static void ViewerDoFindInPagesCB(
	viewer_struct *v, GtkCombo *find_combo, gboolean case_sensitive
);
void ViewerFindInPagesActivateCB(GtkWidget *widget, void *data);

void ViewerIndexCTreeSelectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void ViewerIndexCTreeUnselectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void ViewerIndexCTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
);

void ViewerTextChangeCB(GtkEditable *editable, gpointer data);


#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)

#define ISCR(c)         (((c) == '\n') || ((c) == '\r'))
#define ISBLANK(c)      (((c) == ' ') || ((c) == '\t'))


/*
 *	Viewer Index Item destroy callback.
 */
void ViewerIndexBranchDestroyCB(gpointer data)
{
	viewer_index_item_struct *item = VIEWER_INDEX_ITEM(data);
	if(item == NULL)
	    return;

	ViewerIndexItemDelete(item);
}

/*
 *      Destroy callback.
 */
void ViewerDestroyCB(GtkObject *object, gpointer data)
{

}

/*
 *      Close callback.
 */
gint ViewerCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	static gboolean reenterant = FALSE;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return(TRUE);

	if(!v->initialized)
	    return(TRUE);

	if(reenterant)
	    return(TRUE);
	else
	    reenterant = TRUE;

	/* Check if currently processing */
	if(v->processing)
	{
	    reenterant = FALSE;
	    return(TRUE);
	}


	/* Revord viewer positions and sizes */
	ViewerRecordPositions(v);

	/* Reset and unmap viewer */
	ViewerReset(v, TRUE);

	reenterant = FALSE;
	return(TRUE);
}


/*
 *      Close callback from menu item.
 */
void ViewerCloseMCB(GtkWidget *widget, gpointer data)
{
	ViewerCloseCB(widget, NULL, data);
	return;
}


/*
 *	Close all windows callback.
 */
void ViewerCloseAllCB(GtkWidget *widget, gpointer data)
{
	/* Set global need_close_all_windows to TRUE, this will be checked
	 * when the main manage timeout function MEditManage() is called
	 * and it will begin closing each window.
	 */
	need_close_all_windows = TRUE; 

	return;
}

/*
 *	Switch page callback.
 */
void ViewerSwitchPageCB(
	GtkNotebook *notebook, GtkNotebookPage *page, guint page_num,
	gpointer data
)
{
	static gboolean reenterant = FALSE;
	GtkWidget *w;
	viewer_struct *v = VIEWER(data);
	if((notebook == NULL) || (v == NULL))
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	/* Main notebook? */
	w = v->main_notebook;
	if((void *)w == (void *)notebook)
	{
	    v->current_page = page_num;
	    ViewerUpdateMenus(v);
	}

	/* Add timeout to check for page changed, cannot handle
	 * page changed now since this callback is called just before
	 * the page is changed.
	 */
	gtk_timeout_add(
	    0,
	    (GtkFunction)ViewerPageChangedTOCB,
	    (gpointer)v
	);

	reenterant = FALSE;
	return;
}

/*
 *	Page toggle widgets callback.
 */
void ViewerPageToggleCB(GtkWidget *widget, gpointer data)
{
	static gboolean reenterant = FALSE;
	GtkWidget *w;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	if(widget == v->view_view_mi)
	{
	    w = v->main_notebook;
	    if(w != NULL)
		gtk_notebook_set_page(
		    GTK_NOTEBOOK(w), ViewerPageNumView
		);
	}
	else if(widget == v->view_index_mi)
	{
	    w = v->main_notebook;
	    if(w != NULL)
		gtk_notebook_set_page(
		    GTK_NOTEBOOK(w), ViewerPageNumIndex
		);
	}

	reenterant = FALSE;
	return;
}

/*
 *	Viewer main notebook check page change timeout callback.
 *
 *	This function always returns FALSE.
 */
gint ViewerPageChangedTOCB(gpointer data)
{
	GtkCTree *ctree;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return(FALSE);

	if(!v->initialized)
	    return(FALSE);

	if(v->processing)
	    return(FALSE);

	switch(v->current_page)
	{
	  case ViewerPageNumView:

	    break;

	  case ViewerPageNumIndex:
	    ctree = (GtkCTree *)v->index_ctree;
	    if((ctree == NULL) ?
		0 : (gtk_ctree_node_nth(ctree, 0) == NULL)
	    )
	    {
		ViewerIndexRefreshCB(v->index_refresh_btn, data);
	    }
	    break;
	}

	return(FALSE);
}


/*
 *	Viewer view text keyboard event handler.
 */
gint ViewerViewKeyEventCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data   
)
{
	GtkWidget *w;
	GtkText *text;
	GtkAdjustment *hadj, *vadj;
	gboolean state;
	guint keyval;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL) || (key == NULL))
	    return(FALSE);

	/* Get pointer to view_text widget, event widget must be it */
	w = v->view_text;
	if(w != widget)
	    return(FALSE);

	text = GTK_TEXT(w);
	hadj = text->hadj;
	vadj = text->vadj;
	keyval = key->keyval;
	state = (((*(gint *)key) == GDK_KEY_PRESS) ? TRUE : FALSE);

	/* Important, for some obscure reason the GtkText widget
	 * when set not editable and recieving a key event to insert
	 * text will cause it to internally call gtk_main_quit() and
	 * thus exit or mess up the application.
	 *
	 * Here we play a trick, when the key is pressed the GtkText
	 * widget is set editable but when the key release is recieved
	 * the GtkText is set back not editable.
	 */
	if(state)
	    gtk_editable_set_editable(GTK_EDITABLE(w), TRUE);
	else
	    gtk_editable_set_editable(GTK_EDITABLE(w), FALSE);

	/* Home, scroll all the way back up */
	if(keyval == GDK_Home)
	{
	    if(state)
	    {
		vadj->value = vadj->lower;
		gtk_adjustment_value_changed(vadj);
/* GtkText horizontal scrolling not implmented.
		hadj->value = hadj->lower;
		gtk_adjustment_value_changed(hadj);
 */
	    }
	}
	/* End, scroll to the end */
	else if(keyval == GDK_End)
	{
	    if(state)
	    {
		vadj->value = vadj->upper - vadj->page_size;
		gtk_adjustment_value_changed(vadj);
/* GtkText horizontal scrolling not implmented.
		hadj->value = hadj->upper - hadj->page_size;
		gtk_adjustment_value_changed(hadj);
 */
	    }
	}
	/* Space or enter, scroll one page down */
	else if((keyval == GDK_Return) ||
	        (keyval == ' ') || (keyval == 'n')
	)
	{
	    if(state)
	    {
		vadj->value += vadj->page_size;
		if(vadj->value > (vadj->upper - vadj->page_size))
		    vadj->value = vadj->upper - vadj->page_size;
		gtk_adjustment_value_changed(vadj);
	    }
	}
	/* Backspace or 'b', scroll one page up */
	else if((keyval == GDK_BackSpace) ||
		(keyval == 'b') || (keyval == 'p')
	)
	{
	    if(state)
	    {
		vadj->value -= vadj->page_size;
		if(vadj->value < vadj->lower)
		    vadj->value = vadj->lower;
		gtk_adjustment_value_changed(vadj);
	    }
	}

/*
printf("View text key event %i %i\n", keyval, state);
 */

	return(TRUE);
}



/*
 *	Branch expand/collapse callback (not to handle a signal when
 *	a branch has expanded or collapsed).
 */
void ViewerIndexBranchExpandCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkCTreeRow *branch_row;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	ctree = (GtkCTree *)v->index_ctree;
	if(ctree == NULL)
	    return;

	branch = v->selected_index_branch;
	if(branch == NULL)
	    return;

	branch_row = GTK_CTREE_ROW(branch);
	if(branch_row == NULL)
	    return;

	if(branch_row->is_leaf)
	    return;

	if(branch_row->expanded)
	    gtk_ctree_collapse(ctree, branch);
	else
	    gtk_ctree_expand(ctree, branch);

	return;
}


/*
 *	Open file callback.
 */
void ViewerOpenCB(GtkWidget *widget, gpointer data)
{
	gint status;
	medit_core_struct *core_ptr;
	medit_fetype_list_struct *fetype_list;
	GtkText *text;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	/* Check if currently processing */
	if(v->processing)
	    return;

	core_ptr = (medit_core_struct *)v->core_ptr;
	if(core_ptr == NULL)
	    return;

	text = (GtkText *)v->view_text;
	if(text == NULL)
	    return;

	fetype_list = &core_ptr->fetype_list;

	if(TRUE)
	{
	    gchar *s, **path_rtn;
	    gint path_total_rtns;
	    fb_type_struct *type_rtn;

	    /* Map file browser for opening of manual page output */
	    if(FileBrowserGetResponse(
		"Open Manual Page File",
		"Open", "Cancel",
		v->last_open_path,
		fetype_list->manual_page,
		fetype_list->total_manual_pages,
		&path_rtn, &path_total_rtns, &type_rtn
	    ))
	    {
		if(path_total_rtns > 0)
		{
		    ViewerSetBusy(v);
/* Reset scroll positions when user is calling open callback */
		    v->last_scroll_hpos = 0.0;
		    v->last_scroll_vpos = 0.0;
/*		    ViewerViewTextRecordScrollPositions(v); */
		    ViewerTextDelete(v, 0, -1);
		    ViewerTextInsertPosition(v, 0);
		    status = ViewerLoadFile(v, path_rtn[0], path_rtn[0]);
		    ViewerSetReady(v);

		    switch(status)
		    {
		      case 0:
			/* Success */
			break;

		      /* No such file */
		      case -2:
			s = g_strdup_printf(
"Could not find the manual page file:\n\n    %s",
			    path_rtn[0]
			);
			CDialogGetResponse(
			    "Manual page not found!",
			    s,
"The specified manual page file could not be found\n\
please verify that the given path exists and has\n\
read permissions set.",
			    CDIALOG_ICON_WARNING,
			    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
			    CDIALOG_BTNFLAG_OK
			);
			g_free(s);
			break;   
		       
		      /* Other error */
		      default:
			CDialogGetResponse(
"Manual page opening error!",
"Error occured while opening manual page.",
			    NULL,
			    CDIALOG_ICON_ERROR,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);  
			break;
		    }
		}
		else
		{
		    /* Did not get any paths, abort open */
		    return;
		}
	    }
	    else
	    {
		/* User canceled save as, abort entire open procedure */
		return;
	    }
	}

	/* Update menus, note that a successful load may have updated
	 * menus already but it is safe to do it again here
	 */
	ViewerUpdateMenus(v);
}


/*
 *	Clear callback.
 */
void ViewerClearCB(GtkWidget *widget, gpointer data)
{
	GtkCTreeNode *branch, *prev_branch;
	GtkCTreeRow *branch_row;
	GtkCTree *ctree;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	ViewerSetBusy(v);
	ViewerSetStatusMessage(v, "Clearing contents...");

	switch(v->current_page)
	{
	  case ViewerPageNumView:
	    ViewerTextDelete(v, 0, -1);
	    ViewerUpdateMenus(v);
	    break;

	  case ViewerPageNumIndex:
	    ctree = (GtkCTree *)v->index_ctree;

	    /* Remove all branches on index ctree */
	    gtk_clist_freeze((GtkCList *)ctree);
	    branch = gtk_ctree_node_nth(ctree, 0);
	    while(branch != NULL)
	    {
		prev_branch = branch;	/* Record current branch */

		/* Get branch row data */
		branch_row = GTK_CTREE_ROW(branch);
		/* Get next sibling branch */
		branch = ((branch_row == NULL) ?
		    NULL : branch_row->sibling
		);

		/* Remove current branch */
		gtk_ctree_remove_node(ctree, prev_branch);
	    }
	    gtk_clist_thaw((GtkCList *)ctree);

	    ViewerUpdateMenus(v);
	    break;
	}
	ViewerSetStatusMessage(v, "Clear done");
	ViewerSetReady(v);

	return;
}

/*
 *	Stop callback.
 */
void ViewerStopCB(GtkWidget *widget, gpointer data)
{
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	v->stop_count++;
	if(v->stop_count < 1)
	    v->stop_count = 1;

	return;
}

/*
 *	Viewer index ctree, goto parent item callback.
 */
void ViewerIndexGotoParentCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkCTreeRow *branch_row;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	ctree = (GtkCTree *)v->index_ctree;
	if(ctree == NULL)
	    return;

	/* Get selected branch */
	branch = v->selected_index_branch;
	if(branch == NULL)
	    return;

	branch_row = GTK_CTREE_ROW(branch);

	branch = branch_row->parent;
	if(branch != NULL)
	{
	    gtk_ctree_select(ctree, branch);
	}

	return;
}

/*
 *	Goto selected index ctree branch callback.
 */
void ViewerGotoCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	viewer_index_item_struct *item;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	ctree = (GtkCTree *)v->index_ctree;
	if(ctree == NULL)
	    return;

	/* Get selected branch */
	branch = v->selected_index_branch;
	if(branch == NULL)
	    return;

	item = VIEWER_INDEX_ITEM(
	    gtk_ctree_node_get_row_data(ctree, branch)
	);
	if(item == NULL)
	    return;

	if(item->type == ViewerIndexItemTypeManualPage)
	{
	    if(item->full_path != NULL)
	    {
		ViewerSetBusy(v);
		ViewerViewTextRecordScrollPositions(v);
		ViewerTextDelete(v, 0, -1);
		ViewerTextInsertPosition(v, 0);
		ViewerLoadFile(
		    v, item->full_path, item->full_path
		);
		ViewerSetReady(v);
	    }
	}
}


/*
 *      Index refresh callback.
 */
void ViewerIndexRefreshCB(GtkWidget *widget, gpointer data)
{
	gint i;
	gchar **path = NULL;
	gchar **name = NULL;
	gint total_paths = 0;
	GtkCTree *ctree;
	GtkWidget *w;
	GtkCTreeNode *branch, *prev_branch;
	GtkCTreeRow *branch_row;
	medit_core_struct *core_ptr;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)
	    return;

	if(v->processing)
	    return;

	core_ptr = (medit_core_struct *)v->core_ptr;
	if(core_ptr == NULL)
	    return;

	/* Get pointer to viewer's index ctree */
	ctree = (GtkCTree *)v->index_ctree;
	if(ctree == NULL)
	    return;

	/* Get pointer to pref global manpage dirs clist */
	w = PrefParmGetWidget(
	    core_ptr->pref,
	    MEDIT_PREF_PARM_LOCATIONS_MAN_DIRS
	);
	if(w != NULL)
	{
	    /* Make list of paths and names to load */
	    GtkCList *paths_clist = (GtkCList *)w;

	    total_paths = paths_clist->rows;
	    if(total_paths > 0)
	    {
		gchar *path_ptr, *name_ptr;

		path = (gchar **)g_malloc0(
		    total_paths * sizeof(gchar *)
		);
		name = (gchar **)g_malloc0(
		    total_paths * sizeof(gchar *)
		);
		if((path != NULL) && (name != NULL))
		{
		    for(i = 0; i < total_paths; i++)
		    {
			if(!gtk_clist_get_text(
			    paths_clist,
			    i, 0, &path_ptr
			))
			    path_ptr = NULL;

			if(!gtk_clist_get_text(
			    paths_clist,   
			    i, 1, &name_ptr
			))
			    name_ptr = NULL;

			path[i] = STRDUP(path_ptr);
			name[i] = STRDUP(name_ptr);
		    }
		}
	    }
	}

	/* Remove all existing branches on index ctree */
	gtk_clist_freeze((GtkCList *)ctree);
	branch = gtk_ctree_node_nth(ctree, 0);
	while(branch != NULL)
	{
	    prev_branch = branch;	/* Record current branch */

	    /* Get branch row data */
	    branch_row = GTK_CTREE_ROW(branch);
	    /* Get next sibling branch */
	    branch = ((branch_row == NULL) ? NULL : branch_row->sibling);

	    /* Remove current branch */
	    gtk_ctree_remove_node(ctree, prev_branch);
	}
	gtk_clist_thaw((GtkCList *)ctree);

	/* Load new objects from paths */
	ViewerIndexDoLoad(
	    v, total_paths, path, name
	);

	/* Delete paths and names array */
	strlistfree(path, total_paths);
	strlistfree(name, total_paths);
}  

/*
 *      Index refresh callback.
 */
void ViewerPreferencesCB(GtkWidget *widget, gpointer data)
{
	medit_core_struct *core_ptr;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	core_ptr = (medit_core_struct *)v->core_ptr;
	if(core_ptr != NULL)
	{
	    gchar *s;
	    GtkCTreeNode *branch;
	    GtkCTreeRow *branch_row;
	    GtkCTree *ctree;
	    pref_struct *pref = core_ptr->pref;

	    ctree = (pref != NULL) ?
		(GtkCTree *)pref->catagory_ctree : NULL;
	    if(ctree != NULL)
	    {
		branch = gtk_ctree_node_nth(ctree, 0);
 
		while(branch != NULL)
		{
		    s = PrefPanelGetBranchText(pref, branch);
		    if(s != NULL)
		    {
			if(!g_strcasecmp(s, "Viewer"))
			{
			    gtk_ctree_expand(ctree, branch);
			    gtk_ctree_select(ctree, branch);
			    break;
			}
		    }
	
		    branch_row = GTK_CTREE_ROW(branch);
		    branch = (branch_row != NULL) ?
			branch_row->sibling : NULL;
		}

		PrefDoFetch(pref);
		PrefMap(pref);
	    }
	}
}


/*
 *      Right-click menu mapping.
 */
gint ViewerMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	static gboolean reenterant = FALSE;
	GtkWidget *w;
	GdkEventButton *button;
	viewer_struct *v = VIEWER(data);
	if((v == NULL) || (event == NULL) || (widget == NULL))
	    return(TRUE);

	if(reenterant)
	    return(TRUE);
	else
	    reenterant = TRUE;

	/* Is event type button press? If so then get pointer to button
	 * event structure.
	 */
	if(event->type == GDK_BUTTON_PRESS)
	    button = (GdkEventButton *)event;
	else
	    button = NULL;

	/* View text? */
	if(widget == v->view_text)
	{
	    w = v->view_menu;
	    if((button != NULL) && (w != NULL))
	    {
		if(button->button == 3)
		{
		    /* Map menu */
		    gtk_menu_popup(
			GTK_MENU(w),
			NULL, NULL, NULL, (gpointer)v,
			button->button, button->time
		    );

		    /* Need to mark the text widget's button as 0 or
		     * else it will keep marking.
		     */
		    GTK_TEXT(widget)->button = 0;
		    gtk_grab_remove(widget);
		}
	    }
	}
	/* Index ctree */
	else if (widget == v->index_ctree)
	{
	    w = v->index_menu;
	    if((button != NULL) && (w != NULL))
	    {
		if(button->button == 3)   
		{
		    gint row, column;   
		    GtkCTreeNode *branch;


		    /* Try to select branch button event occured
		     * over first.
		     */
		    if(gtk_clist_get_selection_info(
			(GtkCList *)widget,
			button->x, button->y,
			&row, &column
		    ))
		    {
			branch = gtk_ctree_node_nth(
			    (GtkCTree *)widget, row
			);
			/* Branch valid and not selected? */
			if((branch != NULL) &&
			   (branch != v->selected_index_branch)
			)
			{
			    ViewerBranchSelect(v, branch);
			}
		    }

		    /* Map menu */
		    gtk_menu_popup(
			GTK_MENU(w),
			NULL, NULL, NULL, (gpointer)v,
			button->button, button->time
		    );
		}
	    }

	    /* Double click? */
	    if(event->type == GDK_2BUTTON_PRESS)
	    {
		ViewerGotoCB(widget, data);
	    }
	}

	reenterant = FALSE;
	return(TRUE);
}

/*
 *	Manpage combo activate callback.
 */
void ViewerManPageActivateCB(GtkWidget *widget, void *data)
{
	static gboolean reenterant = FALSE;
	gint section = -2;	/* Any */
	gchar *s, *name, *search_string;
	GtkCombo *combo;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized || v->processing)
	    return;

	if(reenterant)
	    return;   
	else
	    reenterant = TRUE;

	/* Get manpage combo */
	combo = (GtkCombo *)v->manpage_combo;
	if(combo == NULL)
	{
	    reenterant = FALSE;
	    return;
	}

	/* Get category if any */
	if(v->section_combo != NULL)
	{
	    GtkCombo *section_combo = (GtkCombo *)v->section_combo;
	    const gchar *section_str = gtk_entry_get_text(
		GTK_ENTRY(section_combo->entry)
	    );
	    if(section_str != NULL)
	    {
		while(ISBLANK(*section_str))
		    section_str++;

		if(isdigit(*section_str))
		{
		    section = atoi(section_str);
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_1))
		{
		    section = 1;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_2))
		{
		    section = 2;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_3))
		{
		    section = 3;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_4))
		{
		    section = 4;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_5))
		{
		    section = 5;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_6))
		{
		    section = 6;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_7))
		{
		    section = 7;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_8))
		{
		    section = 8;
		}
		else if(!g_strcasecmp(section_str, MEDIT_SECT_NAME_EXACT))
		{
		    section = -1;
		}
		else
		{
		    /* All else assume any (-2) */
		}
	    }
	}


	/* Get search string and make a copy of it */
	search_string = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(combo->entry)
	));

	/* Get name, which is just the search string at this point */
	name = STRDUP(search_string);

	/* Check if search string is empty */
	if((*search_string) == '\0')
	{
	    g_free(search_string);
	    g_free(name);
	    reenterant = FALSE;
	    return;
	}

	/* Add section to beginning of search string */
	switch(section)
	{
	  case -1:
	    /* For exact matches, make sure search_string starts with
	     * a full path
	     */
	    if(*search_string != G_DIR_SEPARATOR)
	    {
		CDialogGetResponse(
"Invalid path!",
"When specifying an exact manual page, the path must be\n\
an absolute path (full path) to the manual page.",
"You need to specify the absolute path (full path) to the\n\
manual page file you are looking for when making exact\n\
matches.",
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);

		g_free(search_string);
		g_free(name);
		reenterant = FALSE;
		return;
	    }
	    break;

	  case -2:
	    break;

	  default:
	    /* Prepend section to the search string */
	    s = g_strdup_printf(
		"%i %s",
		section, search_string
	    );
	    g_free(search_string);
	    search_string = s;
	    break;
	}

	/* Call load file procedure, this will run the conversion
	 * program with the given search string and output any
	 * matched results
	 */
	ViewerSetBusy(v);
	ViewerViewTextRecordScrollPositions(v);
	ViewerTextDelete(v, 0, -1);
	ViewerTextInsertPosition(v, 0);
	ViewerLoadFile(v, search_string, name);
	ViewerSetReady(v);

	g_free(search_string);
	g_free(name);

	ViewerSetStatusMessage(v, "Search done");

	reenterant = FALSE;
}

/*
 *	Procedure to find string in the currently displayed manual
 *	page on the viewer page.
 */
static void ViewerDoFindCB(
	viewer_struct *v, GtkCombo *find_combo, gboolean case_sensitive
)
{
	gboolean got_match, search_wrapped;
	gint start_pos;
	gchar *haystack = NULL, *needle = NULL;
	GtkEditable *editable;
	GtkText *text; 

	if((v == NULL) || (find_combo == NULL))
	    return;

	/* Get pointer to view text */
	text = (GtkText *)v->view_text;
	if(text == NULL)
	    return;

	editable = GTK_EDITABLE(text);

	/* Get needle buffer (coppied) */
	needle = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(find_combo->entry)
	));

	/* Get haystack from text */
	haystack = gtk_editable_get_chars(editable, 0, -1);

	/* Set starting position plus one */
	start_pos = gtk_editable_get_position(editable) + 1;

	/* Do find */
	got_match = ViewerDoFind(
	    v, text,
	    haystack, needle,
	    (gint)gtk_text_get_length(text), start_pos,
	    case_sensitive,
	    TRUE,                   /* Scroll to matched position */
	    &search_wrapped
	);
	/* Did we get a match? */
	if(got_match)
	{
	    /* Search wrapped? */
	    if(search_wrapped)
		CDialogGetResponse(
"Search wrapped!",
"Search wrapped!",
"The matched string was found before the last\n\
cursor position, implying that no further text\n\
was matched after the cursor position.",
		    CDIALOG_ICON_INFO,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
	}   
	else
	{
	    CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched was not found\n\
in the text, implying that the search string\n\
does not exist in the text.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	}

	g_free(haystack);
	g_free(needle);
}

/*
 *	Find in current man page activate combo.
 */
void ViewerFindActivateCB(GtkWidget *widget, void *data)  
{
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(!v->initialized)   
	    return;

	ViewerSetBusy(v);

	ViewerDoFindCB(
	    v,
	    (GtkCombo *)v->find_combo,
	    FALSE
	);

	ViewerUpdateMenus(v);
	ViewerSetReady(v);
}

/*
 *	Checks if the string given by needle exists in the contents of
 *	the file given by path.
 */
static gboolean ViewerFindInPagesDoGrep(
	const gchar *path, gchar *expression, gboolean case_sensitive
)
{
	gboolean got_match;
	gint c;
	glong last_line_start_pos = 0l;
	const gchar *expression_ptr;
	FILE *fp;


	if((path == NULL) || (expression == NULL))
	    return(FALSE);

	/* Open file for reading */
	fp = FOpen(path, "rb");
	if(fp == NULL)
	    return(FALSE);

	got_match = FALSE;
	expression_ptr = expression;

	while(TRUE)
	{
	    /* Get next character */
	    c = fgetc(fp);
	    if(c == EOF)
		break;

	    if(!case_sensitive)
		c = toupper(c);

	    /* If this character marks the end of the line then record
	     * the last line starting position as one character ahead
	     * of the current position
	     */
	    if(ISCR(c))
		last_line_start_pos = ftell(fp);

	    /* This character matches current character of expression?
	     * If so then increment the expression pointer. Otherwise
	     * reset the expression pointer back to the start of the
	     * expression
	     */
	    if((c == *expression_ptr) && (c != '\0'))
		expression_ptr++;
	    else
		expression_ptr = expression;

	    /* Matched entire expression? Test if the expression pointer
	     * has been incremented all the way to the null terminating
	     * character which suggests that all characters in the
	     * expression were matched
	     */
	    if(*expression_ptr == '\0')
	    {
		got_match = TRUE;
		break;
	    }
	}

	/* Close file */
	FClose(fp);

	return(got_match);
}

/*
 *      Procedure to find string from the currently selected manual page
 *	on the index ctree.
 */
static void ViewerDoFindInPagesCB(
	viewer_struct *v, GtkCombo *find_combo, gboolean case_sensitive
)
{
	gboolean got_match;
	gint pass, row_num, start_row;
	gchar *needle = NULL;
	GList *glist;
	GtkCList *clist;
	GtkCTree *ctree;
	GtkCTreeNode *end_node;

	if((v == NULL) || (find_combo == NULL))
	    return;

	/* Get pointer to index ctree */
	ctree = (GtkCTree *)v->index_ctree;
	if(ctree == NULL)
	    return;

	clist = GTK_CLIST(ctree);

	/* Get needle buffer (coppied) */
	needle = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(find_combo->entry)
	));
	if(needle == NULL)
	    return;

	if(!case_sensitive)
	    strtoupper(needle);


	/* Skip if already processing */
	if(v->processing)
	{
	    g_free(needle);
	    return;
	}

	/* Mark as processing and reset stop count to begin search */
	v->processing = TRUE;
	v->stop_count = 0;
	ViewerSetBusy(v);
	ViewerUpdateMenus(v);


	/* Set starting row plus one and the end node */
	start_row = 0;
	end_node = NULL;
	glist = clist->selection_end;
	if((glist != NULL) ? (glist->data != NULL) : FALSE)
	{
	    GList *glist2 = clist->row_list;
	    GtkCTreeRow *ctree_row_ptr = GTK_CTREE_ROW(
		(GtkCTreeNode *)glist->data
	    );
	    GtkCListRow *clist_row_ptr = (GtkCListRow *)ctree_row_ptr;

	    /* Set end node pointer to be the selected node. This will
	     * be encountered on the find second pass farther below
	     */
	    end_node = (GtkCTreeNode *)glist->data;

	    /* Iterate through the row pointers, to find which row pointer
	     * matches the selected row pointer obtained from the selected
	     * tree node. start_row will be incremented to be one past the
	     * selected row index
	     */
	    while(glist2 != NULL)
	    {
		if((gpointer)clist_row_ptr == glist2->data)
		{
		    start_row++;
		    break;
		}

		start_row++;
		glist2 = glist2->next;
	    }
	}


	/* Iterate for two passes */
	got_match = FALSE;
	pass = 0;
	while(pass < 2)
	{
	    GtkCTreeNode *node;
	    GtkCTreeRow *ctree_row_ptr;
	    viewer_index_item_struct *item;

	    row_num = MAX(start_row, 0);

	    for(row_num = MAX(start_row, 0);
		row_num < clist->rows;
		row_num++
	    )
	    {
		if(v->stop_count > 0)
		    break;

		node = gtk_ctree_node_nth(ctree, row_num);
		if((node == NULL) || (node == end_node))
		    break;

		ctree_row_ptr = GTK_CTREE_ROW(node);
		if(ctree_row_ptr == NULL)
		    continue;

		/* Expand this node as needed */
		if(!ctree_row_ptr->expanded && !ctree_row_ptr->is_leaf)
		{
		    /* Expand and skip to next node */
		    gtk_ctree_expand(ctree, node);
		    continue;
		}

		/* Get item data structure */
		item = VIEWER_INDEX_ITEM(
		    gtk_ctree_node_get_row_data(ctree, node)
		);
		if(item == NULL)
		    continue;

		ViewerSetStatusMessage(v, item->full_path);

		/* Check if the string needle exists in the contents of
		 * the file referenced by the item data structure
		 */
		got_match = ViewerFindInPagesDoGrep(
		    item->full_path, needle, case_sensitive
		);
		if(got_match)
		{
		    gtk_ctree_select(ctree, node);
		    break;
		}

		/* Update status bar progress activity */
		ViewerSetStatusProgress(v, -1.0);
	    }

	    if(got_match)
		break;

	    /* Reset start row back to the beginning */
	    start_row = 0;

	    /* Go on to second pass */
	    pass++;
	}

	/* Delete coppied search string */
	g_free(needle);
	needle = NULL;

	v->processing = FALSE;
	ViewerSetReady(v);
	ViewerSetStatusProgress(v, 0.0);
	if(!got_match)
	    ViewerSetStatusMessage(v, "No manual page found");
	ViewerUpdateMenus(v);
}

/*
 *      Find in pages activate combo.
 */
void ViewerFindInPagesActivateCB(GtkWidget *widget, void *data)
{
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	ViewerDoFindInPagesCB(
	    v,
	    (GtkCombo *)v->index_find_in_pages_combo,
	    FALSE
	);
}


/*
 *	Viewer index ctree select callback.
 */
void ViewerIndexCTreeSelectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
	static gboolean reenterant = FALSE;
	gint row;
	GtkCList *clist;
	viewer_struct *v = VIEWER(data);
	if((ctree == NULL) || (v == NULL))
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	if((gpointer)ctree == (gpointer)v->index_ctree)
	{
	    clist = (GtkCList *)ctree;

	    ViewerSetBusy(v);

	    /* A branch selected previously? */
	    if(v->selected_index_branch != NULL)
	    {
		/* Unselect signal will have the values applied, we don't
		 * need to apply values here.
		 */
	    }

	    /* Update selected index branch */
	    v->selected_index_branch = branch;

#if 0 
	    /* Set DND drag icon */
	    ViewerDNDSetIcon(
		v,
		gtk_ctree_node_get_row_data(ctree, branch)
	    );
#endif

	    /* Scroll if row not visibile */
	    row = gtk_clist_find_row_from_data(
		clist,
		gtk_ctree_node_get_row_data(ctree, branch)
	    );
	    if(row > -1)
	    {
		if(gtk_clist_row_is_visible(clist, row) !=
		    GTK_VISIBILITY_FULL
		)
		    gtk_clist_moveto(
			clist,
			row, 0,         /* Row, column */
			0.5, 0.0        /* Row, column */
		    );
	    }



/* Fetch values for newly selected branch? */

/* Notify about selection */

	    /* Update menus */
	    ViewerUpdateMenus(v);

	    ViewerSetReady(v);
	}

	reenterant = FALSE;        
}

/*
 *	Viewer index ctree unselect callback.
 */
void ViewerIndexCTreeUnselectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
	static gboolean reenterant = FALSE;
	viewer_struct *v = VIEWER(data);
	if((ctree == NULL) || (v == NULL))
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	if((gpointer)ctree == (gpointer)v->index_ctree)
	{
	    /* Apply values? */

	    if(branch == v->selected_index_branch)
		v->selected_index_branch = NULL;

	    /* Clear values? */


	    /* Update menus */
	    ViewerUpdateMenus(v);
	}

	reenterant = FALSE;
}

/*
 *      Viewer index ctree branch expand (and collapse) callback, called
 *      whenever a branch is expanded or collapsed.
 */
void ViewerIndexCTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
)
{
	static gboolean reenterant = FALSE;
	viewer_struct *v = VIEWER(data);
	if((ctree == NULL) || (v == NULL))
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	if((gpointer)ctree == (gpointer)v->index_ctree)
	{


	}

	reenterant = FALSE;
}


/*
 *	Viewer view text change callback.
 */
void ViewerTextChangeCB(GtkEditable *editable, gpointer data)
{
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;



}
