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

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
);
gint ViewerViewButtonEventCB(
	GtkWidget *widget, GdkEventButton *button, 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 ViewerCopyCB(GtkWidget *widget, gpointer data);
void ViewerSelectAllCB(GtkWidget *widget, gpointer data);
void ViewerUnselectAllCB(GtkWidget *widget, gpointer data);
void ViewerPreferencesCB(GtkWidget *widget, gpointer data);

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

void ViewerManPageActivateCB(GtkWidget *widget, gpointer data);
static void ViewerFind(
	viewer_struct *v,
	GtkCombo *find_combo,
	const gboolean case_sensitive
);
void ViewerFindActivateCB(GtkWidget *widget, gpointer 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, gpointer 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);
}

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

	if(v->freeze_count > 0)
	    return(TRUE);

	v->freeze_count++;

	/* Revord the viewer's positionss and sizes */
	ViewerRecordPositions(v);

	v->freeze_count--;

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

	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
)
{
	GtkWidget *w;
	viewer_struct *v = VIEWER(data);
	if((notebook == NULL) || (v == NULL))
	    return;

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;

	/* Main notebook? */
	w = v->main_notebook;
	if(w == GTK_WIDGET(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_idle_add(ViewerPageChangedTOCB, v);

	v->freeze_count--;
}

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

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;

	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
		);
	}

	v->freeze_count--;
}

/*
 *	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->freeze_count > 0)
	    return(FALSE);

	switch(v->current_page)
	{
	  case ViewerPageNumView:
	    break;

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

	return(FALSE);
}


/*
 *	View GtkText "key_press_event" or "key_release_event" signal
 *	callback.
 */
gint ViewerViewKeyEventCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data   
)
{
	gint status = FALSE;
	GtkWidget *w;
	GtkText *text;
	GtkAdjustment *hadj, *vadj;
	gboolean state;
	guint keyval;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (key == NULL) || (v == NULL))
	    return(status);

	w = v->view_text;
	if(w != widget)
	    return(status);

	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);
	    }
	}

	status = TRUE;

	return(status);
}

/*
 *	View GtkText "button_press_event" or "button_release_event"
 *	signal callback.
 */
gint ViewerViewButtonEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	GtkText *text;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (button == NULL) || (v == NULL))
	    return(status);

	if(!GTK_IS_TEXT(widget))
	    return(status);

	text = GTK_TEXT(widget);

	switch((gint)button->type)
	{
	  case GDK_BUTTON_PRESS:
	    switch(button->button)
	    {
	      case GDK_BUTTON3:		/* Popup menu */
		/* Map the popup menu */
		gtk_menu_popup(
		    GTK_MENU(v->view_menu),
		    NULL, NULL, NULL, v,
		    button->button, button->time
		);
		/* Need to mark the text widget's button as 0 or
		 * else it will keep marking.
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_press_event"
		);
		status = TRUE;
		break;

	      case GDK_BUTTON4:		/* Scroll up */
		if(text->vadj != NULL)
		{
		    GtkAdjustment *adj = text->vadj;
		    const gfloat inc = MAX(
			(0.25f * adj->page_size),
			adj->step_increment
		    );
		    gfloat v = adj->value - inc;  
		    if(v > (adj->upper - adj->page_size))
			v = adj->upper - adj->page_size;
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		/* Need to mark the GtkText button as 0 or else it will
		 * keep marking
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_press_event"
		);
		status = TRUE;
		break;

	      case GDK_BUTTON5:		/* Scroll down */
		if(text->vadj != NULL)
		{
		    GtkAdjustment *adj = text->vadj;
		    const gfloat inc = MAX(
			(0.25f * adj->page_size),
			adj->step_increment
		    );
		    gfloat v = adj->value + inc;
		    if(v > (adj->upper - adj->page_size))
			v = adj->upper - adj->page_size;
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		/* Need to mark the GtkText button as 0 or else it will
		 * keep marking
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_press_event"
		);
		status = TRUE;
		break;
	    }
	    break;

	  case GDK_BUTTON_RELEASE:
	    switch(button->button)
	    {
	      case GDK_BUTTON3:		/* Popup menu */
		/* Need to mark the GtkText button as 0 or else it will
		 * keep marking
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_release_event"
		);
		status = TRUE;
		break;

	      case GDK_BUTTON4:		/* Scroll up */
		/* Need to mark the GtkText button as 0 or else it will
		 * keep marking
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_release_event"
		);
		status = TRUE;
		break;

	      case GDK_BUTTON5:		/* Scroll down */
		/* Need to mark the GtkText button as 0 or else it will
		 * keep marking
		 */
		text->button = 0;
		gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_release_event"
		);
		status = TRUE;
		break;
	    }
	    break;
	}

	return(status);
}


/*
 *	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;

	ctree = GTK_CTREE(v->index_ctree);
	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)
{
	GtkWidget *toplevel;
	GtkText *text;
	medit_core_struct *core;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;

	toplevel = v->toplevel;
	text = GTK_TEXT(v->view_text);
	core = v->core;

	if(TRUE)
	{
	    gboolean response;
	    gchar **paths_list;
	    gint nftypes = 0, npaths;
	    fb_type_struct **ftypes_list = NULL, *type_rtn;

	    /* Query the user for a manual page */
            FileBrowserTypeListNew(
                &ftypes_list, &nftypes,
                ".1 .2 .3 .4 .5 .6 .bz2 .gz",
                "Manual page"
            );
            FileBrowserTypeListNew(
                &ftypes_list, &nftypes,
                ".mpt",
                "Manual page template"
            );
            FileBrowserTypeListNew(
                &ftypes_list, &nftypes,
                "*.*", "All files"
	    );

	    FileBrowserSetTransientFor(toplevel);
	    response = FileBrowserGetResponse(
		"Open Manual Page",
		"Open", "Cancel",
		v->last_open_path,
		ftypes_list, nftypes,
		&paths_list, &npaths, &type_rtn
	    );
	    FileBrowserSetTransientFor(NULL);
	    FileBrowserDeleteTypeList(ftypes_list, nftypes);

	    if(response && (npaths > 0))
	    {
		gint status;
		gchar *msg;
		const gchar *path = paths_list[0];

		ViewerSetBusy(v);
/* Reset scroll positions when user is calling open callback */
		v->last_scroll_hpos = 0.0f;
		v->last_scroll_vpos = 0.0f;
/*		ViewerViewTextRecordScrollPositions(v); */
		ViewerTextDelete(v, 0, -1);
		ViewerTextInsertPosition(v, 0);
		status = ViewerOpenFile(v, path, path);
		ViewerSetReady(v);

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

		  case -2:		/* No such file */
		    msg = g_strdup_printf(
"Unable to find the manual page file:\n\n    %s",
			path
		    );
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Open Manual Page Failed",
			msg,
"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
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(msg);
		    break;   
		       
		  default:		/* Other error */
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Open Manual Page Failed",
"Error occured while opening manual page.",
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    break;
		}
	    }
	    else
	    {
		/* User canceled save as, abort entire open procedure */
		v->freeze_count--;
		return;
	    }
	}

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

	v->freeze_count--;
}


/*
 *	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->freeze_count > 0)
	    return;

	v->freeze_count++;
	ViewerSetBusy(v);

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

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

	  case ViewerPageNumIndex:
	    ctree = GTK_CTREE(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");

	v->freeze_count--;
	ViewerSetReady(v);
}

/*
 *	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->freeze_count > 0)
	    return;

	ctree = GTK_CTREE(v->index_ctree);

	/* Get the 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);
}

/*
 *	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->freeze_count > 0)
	    return;

	v->freeze_count++;

	ctree = GTK_CTREE(v->index_ctree);

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

	item = VIEWER_INDEX_ITEM(
	    gtk_ctree_node_get_row_data(ctree, branch)
	);
	if(item == NULL)
	{
	    v->freeze_count--;
	    return;
	}

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

	v->freeze_count--;
}


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

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;

	ctree = GTK_CTREE(v->index_ctree);
	core = v->core;

	/* Get pointer to pref global manpage dirs clist */
	w = PrefParmGetWidget(
	    core->pref,
	    MEDIT_PREF_PARM_LOCATIONS_MAN_DIRS
	);
	if(w != NULL)
	{
	    /* Make list of paths and names to load */
	    GtkCList *paths_clist = GTK_CLIST(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(GTK_CLIST(ctree));
	branch = gtk_ctree_node_nth(ctree, 0);
	while(branch != NULL)
	{
	    prev_branch = branch;	/* Record current branch */

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

	    /* Remove the current branch */
	    gtk_ctree_remove_node(ctree, prev_branch);
	}
	gtk_clist_thaw(GTK_CLIST(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);

	v->freeze_count--;
}  


/*
 *	Copy.
 */
void ViewerCopyCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	text = GTK_TEXT(v->view_text);
	editable = GTK_EDITABLE(text);
	gtk_text_freeze(text);
        gtk_editable_copy_clipboard(editable);
	gtk_text_thaw(text);

	ViewerUpdateMenus(v);
	ViewerSetStatusMessage(v, "Selection coppied");
}

/*
 *	Select All.
 */
void ViewerSelectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	text = GTK_TEXT(v->view_text);
	editable = GTK_EDITABLE(text);
	gtk_text_freeze(text);
        gtk_editable_select_region(editable, 0, -1);
	gtk_text_thaw(text);
}

/*
 *	Unselect All.
 */
void ViewerUnselectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	text = GTK_TEXT(v->view_text);
	editable = GTK_EDITABLE(text);
	gtk_text_freeze(text);
        gtk_editable_select_region(editable, -1, -1);
	gtk_text_thaw(text);
}


/*
 *	Preferences.
 */
void ViewerPreferencesCB(GtkWidget *widget, gpointer data)
{
	medit_core_struct *core;
	viewer_struct *v = VIEWER(data);
	if((widget == NULL) || (v == NULL))
	    return;

	core = v->core;
	if(core != NULL)
	{
	    gchar *s;
	    GtkCTreeNode *branch;
	    GtkCTreeRow *branch_row;
	    GtkCTree *ctree;
	    pref_struct *pref = core->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)
	{

	}
	/* 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, gpointer data)
{
	gint section_num = -2;			/* Any */
	gchar *s, *name, *search_string;
	const gchar *section;
	GtkWidget *toplevel;
	GtkCombo *combo;
	pulist_struct *pulist;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return;

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;

	toplevel = v->toplevel;
	combo = GTK_COMBO(v->manpage_combo);

	/* Get the section */
	pulist = PUListBoxGetPUList(v->section_pulistbox);
	section = (const gchar *)PUListGetItemData(
	    pulist,
	    PUListGetSelectedLast(pulist)
	);
	if(section != NULL)
	{
	    if(isdigit(*section))
	    {
		section_num = ATOI(section);
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_1))
	    {
		section_num = 1;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_2))
	    {
		section_num = 2;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_3))
	    {
	        section_num = 3;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_4))
	    {
		section_num = 4;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_5))
	    {
		section_num = 5;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_6))
	    {
		section_num = 6;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_7))
	    {
		section_num = 7;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_8))
	    {
		section_num = 8;
	    }
	    else if(!g_strcasecmp(section, MEDIT_SECT_NAME_EXACT))
	    {
		section_num = -1;
	    }
	    else
	    {
		/* All else assume any (-2) */
	    }
	}

	/* Get the search string */
	search_string = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(combo->entry)
	));

	/* Get the name (which is just the search string) */
	name = STRDUP(search_string);
	if(STRISEMPTY(search_string))
	{
	    g_free(search_string);
	    g_free(name);
	    v->freeze_count--;
	    return;
	}

	/* Add section to beginning of search string */
	switch(section_num)
	{
	  case -1:
	    /* For exact matches, make sure search_string starts with
	     * a full path
	     */
	    if(*search_string != G_DIR_SEPARATOR)
	    {
		CDialogSetTransientFor(toplevel);
		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
		);
		CDialogSetTransientFor(NULL);
		g_free(search_string);
		g_free(name);
		v->freeze_count--;
		return;
	    }
	    break;

	  case -2:
	    break;

	  default:
	    /* Prepend section to the search string */
	    s = g_strdup_printf(
		"%i %s",
		section_num, 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);
	ViewerOpenFile(v, search_string, name);
	ViewerSetReady(v);

	g_free(search_string);
	g_free(name);

	ViewerSetStatusMessage(v, "Search done");

	v->freeze_count--;
}

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

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

	toplevel = v->toplevel;
	text = GTK_TEXT(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)
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Search Wrapped",
"Search wrapped.",
		    NULL,
		    CDIALOG_ICON_INFO,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	}   
	else
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"String Not Found",
"The string was not found.",
		NULL,
		CDIALOG_ICON_SEARCH,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	}

	g_free(haystack);
	g_free(needle);
}

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

	if(v->freeze_count > 0)
	    return;

	v->freeze_count++;
	ViewerSetBusy(v);

	ViewerFind(
	    v,
	    GTK_COMBO(v->find_combo),
	    FALSE
	);

	ViewerUpdateMenus(v);

	v->freeze_count--;
	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;

	ctree = GTK_CTREE(v->index_ctree);
	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.0f);
	if(!got_match)
	    ViewerSetStatusMessage(v, "No manual page found");
	ViewerUpdateMenus(v);
}

/*
 *      Find in pages activate combo.
 */
void ViewerFindInPagesActivateCB(GtkWidget *widget, gpointer 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;



}
