#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

#include "guiutils.h"
#include "pulist.h"
#include "fb.h"

#include "viewer.h"
#include "viewercb.h"
#include "viewerdnd.h"
#include "editordnd.h"
#include "manedit.h"
#include "maneditcb.h"
#include "maneditop.h"
#include "messages.h"
#include "config.h"


#include "images/manedit_16x16.xpm"
#include "images/mp_viewer_16x16.xpm"
#include "images/manedit_20x20.xpm"
#include "images/mp_viewer_20x20.xpm"
#include "images/mp_viewer_48x48.xpm"

#include "images/icon_manual_opened_20x20.xpm"
#include "images/icon_search_20x20.xpm"
#include "images/icon_open_20x20.xpm"
#include "images/icon_close_20x20.xpm"
#include "images/icon_clear_20x20.xpm"
#include "images/icon_copy_20x20.xpm"
#include "images/icon_stop_20x20.xpm"
#include "images/icon_exit_20x20.xpm"
#include "images/icon_goto_20x20.xpm"
#include "images/icon_folder_parent_20x20.xpm"
#include "images/icon_reload_20x20.xpm"
#include "images/icon_options_20x20.xpm"


viewer_index_item_struct *ViewerIndexItemNew(
	viewer_index_item_type type, const gchar *full_path
);
void ViewerIndexItemDelete(viewer_index_item_struct *item);
void ViewerBranchSelect(
	viewer_struct *v, GtkCTreeNode *branch
);

void ViewerTextInsertPosition(viewer_struct *v, gint start_pos);
void ViewerTextInsert(
	viewer_struct *v, const gchar *buf, const gint buf_len,
	gint (*progress_cb)(const gulong, const gulong, gpointer),
	gpointer progress_data
);
void ViewerTextDelete(viewer_struct *v, gint start_pos, gint end_pos);

static GtkCTreeNode *ViewerIndexDoLoadIteration(
	viewer_struct *v, GtkCTree *ctree,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	const gchar *full_path, const gchar *name
);
void ViewerIndexDoLoad(  
	viewer_struct *v,
	gint total_paths, gchar **path, gchar **name
);

gboolean ViewerDoFind(
	viewer_struct *v, GtkText *text,
	gchar *haystack, gchar *needle,
	gint haystack_len, gint start_pos,
	gboolean case_sensitive,
	gboolean move_to,
	gboolean *search_wrapped
);

void ViewerViewTextRecordScrollPositions(viewer_struct *v);

void ViewerSetBusy(viewer_struct *v);
void ViewerSetReady(viewer_struct *v);

static void ViewerCreateMenuBar(viewer_struct *v, GtkWidget *parent);
static void ViewerCreateToolBar(viewer_struct *v, GtkWidget *parent);
static void ViewerCreateStatusBar(viewer_struct *v, GtkWidget *parent);

viewer_struct *ViewerNew(gpointer core_ptr, gint editor_num);
void ViewerUpdateMenus(viewer_struct *v);
void ViewerSetStatusPosition(viewer_struct *v, gint row, gint column);
void ViewerSetStatusMessage(viewer_struct *v, const gchar *mesg);
void ViewerSetStatusProgress(viewer_struct *v, gfloat percent);
void ViewerRecordPositions(viewer_struct *v);
void ViewerReset(viewer_struct *v, const gboolean unmap);
void ViewerMap(viewer_struct *v);
void ViewerUnmap(viewer_struct *v);
void ViewerDelete(viewer_struct *v);


#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 VIEWER_BTN_WIDTH	(100 + (2 * 3))
#define VIEWER_BTN_HEIGHT	(30 + (2 * 3))


/*
 *	Creates a new Viewer Index Item.
 */
extern viewer_index_item_struct *ViewerIndexItemNew(
	viewer_index_item_type type, const gchar *full_path
)
{
	viewer_index_item_struct *item = VIEWER_INDEX_ITEM(g_malloc0(
	    sizeof(viewer_index_item_struct)
	));
	if(item == NULL)
	    return(NULL);

	item->type = type;
	item->full_path = STRDUP(full_path);

	return(item);
}

/*
 *	Deletes a viewer index item structure.
 */
void ViewerIndexItemDelete(viewer_index_item_struct *item)
{
	if(item == NULL)
	    return;

	g_free(item->full_path);
	g_free(item);
}


/*
 *	Selects the given branch on the viewer's index ctree.
 *
 *      Expands the parent branches if any and as needed.
 *
 *      Will call expand, select, and unselect callbacks.
 */
void ViewerBranchSelect(
	viewer_struct *v, GtkCTreeNode *branch
)
{
	GtkCTreeNode *parent;
	GtkCTreeRow *branch_row;
	GtkCTree *ctree;

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

	ctree = GTK_CTREE(v->index_ctree);

	/* Get parent branch of given branch */
	branch_row = GTK_CTREE_ROW(branch);
	parent = (branch_row != NULL) ?
	    branch_row->parent : NULL;
	/* Get row of parent branch (if it is not NULL) */
	branch_row = (parent != NULL) ?
	    GTK_CTREE_ROW(parent) : NULL;
	/* Is parent branch expanded? */
	if((branch_row == NULL) ? 0 : !(branch_row->expanded))
	{
	    /* Expand all parent branches */
	    while(parent != NULL)
	    {
		/* Expand parent branch (this will generate an expand
		 * signal which will be handled).
		 */
		gtk_ctree_expand(ctree, parent);

		/* Get parent of parent branch */
		branch_row = GTK_CTREE_ROW(parent);
		parent = ((branch_row == NULL) ?
		    NULL : branch_row->parent   
		); 
	    }
	}

/*      gtk_ctree_node_moveto(ctree, branch, 0, 0, 0); */

	/* Select the branch, this will call the unselect and then
	 * select callbacks.
	 */
	gtk_ctree_select(ctree, branch);

	return;
}


/*
 *	Sets the starting text insert position for the given viewer's
 *	text widget. If start_pos is -1 then it will be set to the end
 *	of the text widget's text.
 */
void ViewerTextInsertPosition(viewer_struct *v, gint start_pos)
{
	GtkWidget *w;
	GtkText *text;
	gint end_pos;


	if(v == NULL)
	    return;

	/* Get view text widget pointer */
	w = v->view_text;
	if(w == NULL)
	    return;
	else
	    text = GTK_TEXT(w);

	/* Get length of text */
	end_pos = gtk_text_get_length(text);

	if(start_pos < 0)
	    start_pos = end_pos;
	if(start_pos > end_pos)
	    start_pos = end_pos;

	gtk_text_set_point(text, start_pos);

	return;
}

/*
 *	Inserts the given buffer to the viewer's view text parsed.
 *
 *	If buf_len is -1 then the entire given buf will be inserted,
 *	and in which case the given buf needs to be null terminated.
 *
 *	Calling function is responsible for setting the starting
 *	text insert position.
 *
 *	If the callback function is not NULL and returns non-zero then
 *	the insert will be aborted.
 */
void ViewerTextInsert(
	viewer_struct *v, const gchar *buf, const gint buf_len,
	gint (*progress_cb)(const gulong, const gulong, gpointer),
	gpointer progress_data
)
{
	gint _buf_len = buf_len;
	GtkWidget *w;
	GtkText *text;
	gchar last_char;
	const gchar *buf_ptr, *buf_next, *buf_end;
	medit_core_struct *core;
	medit_styles_list_struct *styles_list;
	GdkFont *cur_font = NULL;
	GtkStyle *style;

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

	w = v->view_text;
	text = GTK_TEXT(w);
	core = v->core;
	styles_list = &core->styles_list;

	if(_buf_len < 0)
	    _buf_len = STRLEN(buf);

	if(_buf_len == 0)
	    return;

	/* Set the current standard font */
	style = styles_list->manpage_text_standard;
	cur_font = (style != NULL) ? style->font : NULL;

#define IS_CTL_CHAR(c)	(((c) == 0x08))

	gtk_text_freeze(text);

	/* Iterate through buffer */
	last_char = '\0';	/* Reset last character to be printed */
	buf_ptr = buf;
	buf_end = (const gchar *)(buf + _buf_len);
	while(buf_ptr < buf_end)
	{
	    if(progress_cb != NULL)
	    {
#if 0
/* Do not call function callback while GtkText widget is frozen */
		status = progress_cb(
		    (gulong)MIN(buf_ptr - buf, _buf_len), (gulong)_buf_len,
		    progress_data
		);
		if(status != 0)
		    break;
#endif
	    }

	    /* Seek to next control charater if any, setting buf_next
	     * to point to the next control character or end of buffer
	     */
	    buf_next = buf_ptr;
	    while(buf_next < buf_end)
	    {
		if(IS_CTL_CHAR(*buf_next))
		    break;
		else
		    buf_next++;
	    }
	    /* Current character a control character? */
	    if(IS_CTL_CHAR(*buf_ptr))
	    {
		/* Increment buf_ptr past control character */
		buf_ptr++;

		/* Change style */

		/* Last print character an underline? */
		if(last_char == '_')
		{
		    style = styles_list->manpage_text_underline;
		}
		/* Last print character same as this one? */
		else if((buf_ptr < buf_end) ?
		    (last_char == (*buf_ptr)) : 0
		)
		{
		    style = styles_list->manpage_text_bold;
		}
		else
		{
		    style = styles_list->manpage_text_standard;
		}
		cur_font = (style != NULL) ? style->font : NULL;


		/* Delete one character backwards */
		gtk_text_backward_delete(text, 1);

		/* Increment and insert next character */
		if(buf_ptr < buf_end)
		{
		    gtk_text_insert(
			text,
			cur_font,
			(style != NULL) ?
			    &style->fg[GTK_STATE_NORMAL] : NULL,
			NULL,
			buf_ptr,
			1
		    );
/*
printf("Insert one: %.8x %.8x (%.8x)\n",
 (guint)buf_ptr, (guint)(buf_ptr + 1), (guint)buf_end);
 */
		}

		/* Increment to next character */
		buf_ptr++;
	    }
	    else
	    {
		/* Store to next control character or end of buffer */

		/* Calculate this segment length */
		gint seg_len = (gint)(buf_next - buf_ptr);

		/* Use ManPage standard text style */
		style = styles_list->manpage_text_standard;
		cur_font = (style != NULL) ? style->font : NULL;

		/* Any characters to store? */
		if(seg_len > 0)
		{
		    gtk_text_insert(
			text,
			cur_font,
			(style == NULL) ?
			    NULL : &style->fg[GTK_STATE_NORMAL],
			NULL,
			buf_ptr,
			seg_len
		    );
/*
printf("Insert %i: %.8x %.8x (%.8x)\n", seg_len,
 (guint)buf_ptr, (guint)buf_next, (guint)buf_end);
 */
		}

		/* Seek past this segment */
		buf_ptr = (const gchar *)(buf_ptr + MAX(seg_len, 1));

		/* Record last character to be printed */
		if((buf_ptr <= buf_end) && (buf_ptr > buf))
		    last_char = (*(buf_ptr - 1));
	    }
	}

	/* Clear the selection */
	gtk_editable_select_region(GTK_EDITABLE(text), -1, -1);

#undef IS_CTL_CHAR

	gtk_text_thaw(text);
}

/*
 *	Deletes the segment of text.
 *
 *	If end_pos is -1 then all text will be deleted from start_pos.
 */
void ViewerTextDelete(viewer_struct *v, gint start_pos, gint end_pos)
{
	GtkWidget *w;

	if(v == NULL)
	    return;

	w = v->view_text;

	gtk_text_freeze(GTK_TEXT(w));
	gtk_editable_delete_text(
	    GTK_EDITABLE(w),
	    start_pos, end_pos
	);
	gtk_text_thaw(GTK_TEXT(w));
}

/*
 *	Loads the given manual page or directory specified
 *	by full_path. If full_path is a directory then each file or
 *	directory within it will be loaded as well.
 *
 *	Inputs assumed valid.
 */
static GtkCTreeNode *ViewerIndexDoLoadIteration(
	viewer_struct *v, GtkCTree *ctree,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	const gchar *full_path, const gchar *name
)
{
	struct stat stat_buf;
	gint i, text_total;
	gchar **text;
	GtkCTreeNode *new_branch = NULL;
	viewer_index_item_struct *item;
	medit_core_struct *core;
	medit_pixmaps_list_struct *pixmaps_list;

	/* Check for stop */
	if(v->stop_count > 0)
	    return(new_branch);

	core = v->core;
	if(core == NULL)
	    return(new_branch);

	pixmaps_list = &core->pixmaps_list;

	if(full_path == NULL)
	    return(new_branch);

	if(stat(full_path, &stat_buf))
	    return(new_branch);

	if(S_ISDIR(stat_buf.st_mode))
	{
	    text_total = 4;
	    text = (gchar **)g_malloc0(text_total * sizeof(gchar *));
	    if(text != NULL)
	    {
		/* Allocate name cell value */
		if(name != NULL)
		{
		    text[0] = STRDUP(name);
		}
		else
		{
		    gchar *s = strrchr(full_path, G_DIR_SEPARATOR);
		    text[0] = STRDUP((s != NULL) ? (s + 1) : full_path);
		}

		/* Substitute directory name with verbose catagory
		 * name if possible
		 */
		if(!strcmp(text[0], "man1"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_1);
		}
		else if(!strcmp(text[0], "man2"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_2);
		}
		else if(!strcmp(text[0], "man3"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_3);
		}
		else if(!strcmp(text[0], "man4"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_4);
		}
		else if(!strcmp(text[0], "man5"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_5);
		}
		else if(!strcmp(text[0], "man6"))
		{       
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_6);
		}
		else if(!strcmp(text[0], "man7"))
		{
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_7);
		}
		else if(!strcmp(text[0], "man8"))
		{ 
		    g_free(text[0]);
		    text[0] = STRDUP(MEDIT_SECT_NAME_8);
		}
		/* Now strip .extension from name */
		if(text[0] != NULL)
		{
		    gchar *s = strrchr(text[0], '.');
		    if(s != NULL)
			*s = '\0';
		}

		/* Allocate lines and read state cell values */
		text[1] = STRDUP("");
		text[2] = STRDUP("");

		/* Allocate description value */
		text[3] = STRDUP("*Directory*");

		/* Create new branch */
		new_branch = gtk_ctree_insert_node(
		    ctree, parent, sibling,
		    text,
		    MEDIT_LIST_ICON_TEXT_SPACING,
		    pixmaps_list->folder_closed_20x20,
		    pixmaps_list->folder_closed_20x20_mask,
		    pixmaps_list->folder_opened_20x20,
		    pixmaps_list->folder_opened_20x20_mask,
		    FALSE,		/* Is leaf */
		    FALSE		/* Expanded */
		);

		/* Allocate new index item structure */
		item = ViewerIndexItemNew(
		    ViewerIndexItemTypeDirectory,	/* Type */
		    full_path				/* Full path */
		);
		/* Set index item structure pointer to branch row data */
		gtk_ctree_node_set_row_data_full(
		    ctree, new_branch,
		    item, ViewerIndexBranchDestroyCB
		);

		/* Delete cell values */
		strlistfree(text, text_total);
	    }
	}
	else
	{
	    text_total = 4;
	    text = (gchar **)g_malloc0(text_total * sizeof(gchar *));
	    if(text != NULL)
	    {
		/* Get name cell value */
		gchar *s = strrchr(full_path, G_DIR_SEPARATOR);
		text[0] = STRDUP((s != NULL) ? (s + 1) : full_path);
		/* Now strip .extension from name */
		if(text[0] != NULL)
		{
		    gchar *s = strrchr(text[0], '.');
		    if(s != NULL)
			*s = '\0';
		}

		/* Get lines and read state cell values */
		text[1] = STRDUP("");
		text[2] = STRDUP("");

		/* Get description cell value */
		text[3] = STRDUP("Manual Page File");

		/* Create new branch */
		new_branch = gtk_ctree_insert_node(
		    ctree, parent, sibling,
		    text,
		    MEDIT_LIST_ICON_TEXT_SPACING,
		    pixmaps_list->manual_closed_20x20,
		    pixmaps_list->manual_closed_20x20_mask,
		    pixmaps_list->manual_opened_20x20,
		    pixmaps_list->manual_opened_20x20_mask,
		    TRUE,		/* Is leaf */
		    FALSE		/* Expanded */
		);

		/* Allocate new index item structure */
		item = ViewerIndexItemNew(
		    ViewerIndexItemTypeManualPage,	/* Type */
		    full_path				/* Full path */
		);
		/* Set index item structure pointer to branch row data */
		gtk_ctree_node_set_row_data_full(
		    ctree, new_branch,
		    item, ViewerIndexBranchDestroyCB
		);

		/* Delete cell values */
		strlistfree(text, text_total);
	    }
	}


	/* If given path is a directory, recurse into it */
	if(S_ISDIR(stat_buf.st_mode))
	{
	    gchar **path;

	    /* Print status message */
	    gchar *s = g_strdup_printf(
		"Scanning %s...",
		full_path
	    );
	    ViewerSetStatusMessage(v, s);
	    g_free(s);

	    while(gtk_events_pending() > 0)
		gtk_main_iteration();

	    /* Check for stop */
	    if(v->stop_count > 0)
		return(new_branch);


	    /* Get dir entries */
	    path = GetDirEntNames(full_path);

	    for(i = 0; path[i] != NULL; i++);
	    path = StringQSort(path, i);

	    /* Iterate through each path */
	    for(i = 0; path[i] != NULL; i++)
	    {
		/* Valid directory or file name? */
		if(strcmp(path[i], ".") &&
		   strcmp(path[i], "..") &&
		   strcmp(path[i], "whatis")
		)
		{
		    /* Make into full path as strptr */
		    s = STRDUP(PrefixPaths(full_path, path[i]));
		    if(s != NULL)
		    {
			ViewerIndexDoLoadIteration(
			    v, ctree,
			    new_branch,		/* Parent */
			    NULL,		/* Sibling */
			    s, NULL
			);
			g_free(s);
		    }
		}

		/* Delete this path */
		g_free(path[i]);
		path[i] = NULL;
	    }

	    /* Delete paths pointer array (note each pointed to path
	     * was deleted in the above loop)
	     */
	    g_free(path);
	}

	return(new_branch);
}

/*
 *	Loads recursivly, manual pages in the given paths array.
 *
 *	The given paths must be full paths. The given paths and names
 *	list will not be deleted. The names will be used as the
 *	directory names placed into the index clist, each name corresponds
 *	to the path.
 */
void ViewerIndexDoLoad(
	viewer_struct *v,
	gint total_paths, gchar **path, gchar **name
)
{
	gint i;
	gchar *s;
	GtkCTree *ctree;

	if((v == NULL) || (path == NULL) || (total_paths < 1))
	    return;

	ctree = GTK_CTREE(v->index_ctree);

	v->processing = TRUE;
	v->stop_count = 0;

	gtk_clist_freeze(GTK_CLIST(ctree));

	ViewerSetBusy(v);
	ViewerUpdateMenus(v);


	/* Iterate through each path */
	for(i = 0; i < total_paths; i++)
	{
	    if(path[i] == NULL)
		continue;

	    /* Manage GTK events */
	    while(gtk_events_pending() > 0)
		gtk_main_iteration();

	    /* Check for stop */
	    if(v->stop_count > 0)
		break;

	    /* Print status message */
	    s = g_strdup_printf(
		"Scanning %s...",
		path[i]
	    );
	    ViewerSetStatusMessage(v, s);
	    g_free(s);

	    /* Load this path and any subdirs */
	    ViewerIndexDoLoadIteration(
		v, ctree,
		NULL,		/* Parent */
		NULL,		/* Sibling */
		path[i], name[i]
	    );
	}

	ViewerSetStatusMessage(v, "Index scan done");

	gtk_clist_thaw(GTK_CLIST(ctree));

	v->processing = FALSE;
	ViewerSetReady(v);
	ViewerUpdateMenus(v);
}


/*
 *      Procedure to find needle from haystack with respect to
 *      the given text widget.
 *
 *      Both needle and haystack may be modified by this function,
 *      but never deleted.
 *
 *      Given GtkText widget will be updated and instructed to select
 *      the matched text if a match was made. If case_sensitive is TRUE
 *      then the search will be made case sensitive, and if move_to is
 *      set to TRUE then gtk_editable_set_position() will be called to
 *      scroll to the new matched position.
 *
 *      If haystack_len is -1 then strlen() will be called on haystack
 *      to get the actual length. Given start_pos will be sanitized,
 *      if start_pos is >= haystack_len then start_pos will be set to 0.
 *
 *      Returns TRUE if a match is made.
 *
 *      Pointer to search_wrapped will be set to TRUE if a match
 *      was made after a search wrapped through the entire buffer.
 */
gboolean ViewerDoFind(
	viewer_struct *v, GtkText *text,
	gchar *haystack, gchar *needle,
	gint haystack_len, gint start_pos,
	gboolean case_sensitive,
	gboolean move_to,
	gboolean *search_wrapped
)
{
	gchar *bp, *match_ptr;


	/* Reset search wrapped */
	if(search_wrapped != NULL)
	    (*search_wrapped) = FALSE;

	/* Inputs valid? */
	if((v == NULL) || (text == NULL) ||
	   (haystack == NULL) || (needle == NULL)
	)
	    return(FALSE);

	/* Need to get haystack length ourself? */
	if(haystack_len < 0)
	    haystack_len = strlen(haystack);

	/* Empty haystack? */
	if(haystack_len < 1)
	    return(FALSE);

	/* Empty needle? */
	if((*needle) == '\0')
	    return(FALSE);

	ViewerSetStatusMessage(v, "Finding...");
	
	/* Sanitize starting position */
	if(start_pos >= haystack_len)
	    start_pos = 0;
	else if(start_pos < 0)
	    start_pos = 0;
 
	
	/* Case insensitive search? */
	if(!case_sensitive)
	{
	    /* Need to modify buffers to upper case */
	    
	    /* Modify ending portion of haystack */
	    bp = (gchar *)(haystack + start_pos);
	    while((*bp) != '\0')
	    {
		(*bp) = toupper(*bp);
		bp++;
	    }
	
	    /* Modify needle */
	    bp = needle;
	    while((*bp) != '\0')
	    {
		(*bp) = toupper(*bp);
		bp++;
	    }
	}
	
	    
	/* Begin search from starting position */
	match_ptr = strstr(
	    (gchar *)(haystack + start_pos),
	    needle
	);
	if(match_ptr == NULL)
	{
	    /* Did not get match, so start from beginning of haystack
	     * and search again. First check if this is case insensitive.
	     */
	    if(!case_sensitive)
	    {
		/* Toupper the beginning portion of the buffer */
		bp = haystack;
		while(bp < (gchar *)(haystack + start_pos))
		{
		    (*bp) = toupper(*bp);
		    bp++;
		}
	    }
	    /* Now try match again for the second time starting form the
	     * beginning.
	     */
	    match_ptr = strstr( 
		(gchar *)(haystack + 0),
		needle
	    );
	    if(match_ptr != NULL)
	    {
		/* Got match the second time, now mark search wrapped
		 * as true.
		 */
		if(search_wrapped != NULL)
		    (*search_wrapped) = TRUE;
	    }
	}
	    
	ViewerSetStatusMessage(v, "Find done");
	
	/* Did we get a match? */
	if(match_ptr == NULL)
	{
	    /* Sorry, no match made */
	    return(FALSE);
	}
	else
	{
	    /* Got match */
	    gint match_start_pos = (gint)(match_ptr - haystack);
	    gint needle_len = strlen(needle);
		    
	    if(match_start_pos < 0)
		match_start_pos = 0;
	    
	    /* Now modify the text widget, set new cursor position and
	     * select the matched string.
	     */
	    if(1)
	    {
		GtkEditable *editable = (GtkEditable *)text;
	    
		/* Scroll to new matched position? */
		if(move_to)
		{
		    gtk_text_set_point(text, match_start_pos);
		    gtk_editable_set_position(
			editable, match_start_pos
		    );
		}

		if(needle_len > 0)
		{
		    gtk_editable_select_region(
			editable,
			match_start_pos,
			match_start_pos + needle_len
		    );
		}
	    }

	    return(TRUE);
	}
}


/*
 *	Records the viewer's view text scroll bar position values.
 */
void ViewerViewTextRecordScrollPositions(viewer_struct *v)
{
	GtkText *text;
	GtkAdjustment *adj;


	if(v == NULL)
	    return;

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

	adj = text->hadj;
	if(adj != NULL)
	    v->last_scroll_hpos = adj->value;

	adj = text->vadj;
	if(adj != NULL)
	    v->last_scroll_vpos = adj->value;

	return;
}


/*
 *      Blocks input and sets viewer as marked busy.
 */
void ViewerSetBusy(viewer_struct *v)
{ 
	GtkWidget *w;
	GdkCursor *cur;
	medit_core_struct *core;


	if(v == NULL)
	    return;

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

	w = v->toplevel;
	if(w == NULL)
	    return; 
  
	cur = core->cursors_list.busy;
	if(cur == NULL)
	    return;

	if(GTK_WIDGET_NO_WINDOW(w))
	    return;

	gdk_window_set_cursor(w->window, cur);
	gdk_flush();
}  

/*
 *      Allows input and sets viewer as ready for input.
 */
void ViewerSetReady(viewer_struct *v)
{           
	GtkWidget *w;
  
	if(v == NULL)
	    return;
   
	w = v->toplevel;
	if(w == NULL)
	    return;
 
	if(GTK_WIDGET_NO_WINDOW(w))
	    return;

	gdk_window_set_cursor(w->window, NULL);
	gdk_flush();

	return;
}


/* 
 *      Creates the menu bar for the viewer, this function should be
 *      called by ViewerNew().
 *
 *      Given parent is assumed to be a vbox.
 */
static void ViewerCreateMenuBar(viewer_struct *v, GtkWidget *parent)
{
	guint accel_key, accel_mods;
	gchar *label = NULL;
	guint8 **icon;
	gpointer old_client_data, client_data = v;
	GtkAccelGroup *accelgrp;
	GtkWidget *menu_bar, *parent2, *menu, *w, *fw;
	void (*func_cb)(GtkWidget *w, gpointer);

	/* Create the menu bar */
	v->menu_bar = menu_bar = GUIMenuBarCreate(&accelgrp);
	gtk_widget_show(menu_bar);

#define DO_ADD_MENU_ITEM_LABEL          \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp, \
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  client_data, func_cb \
 ); \
}

#define DO_ADD_MENU_ITEM_SUBMENU        \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_SUBMENU, accelgrp, \
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  client_data, func_cb \
 ); \
 if(w != NULL) \
  GUIMenuItemSetSubMenu(w, submenu); \
}
	
#define DO_ADD_MENU_ITEM_CHECK          \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp, \
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  client_data, func_cb \
 ); \
}
	
#define DO_ADD_MENU_SEP         \
{ \
w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL, \
  NULL, NULL, 0, 0, NULL, \
  NULL, NULL \
 ); \
}

	/* Create file menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = (guint8 **)icon_open_20x20_xpm;
	    label = "Open...";
	    accel_key = GDK_o;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerOpenCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->open_mi = w;

	    icon = (guint8 **)icon_clear_20x20_xpm;
	    label = "Clear";
	    accel_key = GDK_k;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerClearCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->clear_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_close_20x20_xpm;
	    label = "Close";
	    accel_key = GDK_w;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerCloseMCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->close_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_exit_20x20_xpm;
	    label = "Exit";
	    accel_key = GDK_q;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerCloseAllCB;
	    DO_ADD_MENU_ITEM_LABEL 
	    v->exit_mi = w;
	}
	v->file_mh = GUIMenuAddToMenuBar(
	    menu_bar, menu,
	    "File",
	    GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Create edit menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = (guint8 **)icon_copy_20x20_xpm;
	    label = "Copy";
	    accel_key = GDK_c;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerCopyCB;
	    DO_ADD_MENU_ITEM_LABEL 
	    v->edit_copy_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Select All";
	    accel_key = GDK_a;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerSelectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->edit_select_all_mi = w;

	    icon = NULL;
	    label = "Unselect All";
	    accel_key = GDK_u;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerUnselectAllCB;
	    DO_ADD_MENU_ITEM_LABEL 
	    v->edit_unselect_all_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_options_20x20_xpm;
	    label = "Preferences...";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerPreferencesCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->edit_preferences_mi = w;
	}
	v->edit_mh = GUIMenuAddToMenuBar(
	    menu_bar, menu,
	    "Edit",
	    GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Create view menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = NULL;
	    label = "Manual Page";
	    accel_key = GDK_1;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerPageToggleCB;
	    DO_ADD_MENU_ITEM_CHECK
	    v->view_view_mi = w;

	    icon = NULL;
	    label = "Index";
	    accel_key = GDK_2;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerPageToggleCB;
	    DO_ADD_MENU_ITEM_CHECK
	    v->view_index_mi = w;
	}
	v->view_mh = GUIMenuAddToMenuBar(
	    menu_bar, menu,
	    "View",
	    GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Create windows menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    old_client_data = client_data;
	    client_data = v->core;

	    icon = (guint8 **)manedit_20x20_xpm;
	    label = "New Editor";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = MEditEditorNewCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->windows_new_editor = w;

	    icon = (guint8 **)mp_viewer_20x20_xpm;
	    label = "New Viewer";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = MEditViewerNewCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->windows_new_viewer = w;

	    client_data = old_client_data;
	}
	v->windows_mh = GUIMenuAddToMenuBar(
	    menu_bar, menu,
	    "Windows",
	    GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Help menu */
	menu = MEditCreateHelpMenu(
	    v->core, accelgrp
	);
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
	    "Help",   
	    GUI_MENU_BAR_ALIGN_RIGHT
	);

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_SUBMENU
#undef DO_ADD_MENU_ITEM_CHECK 
#undef DO_ADD_MENU_SEP


	/* Handle for menu bar */
	w = gtk_handle_box_new();
	v->menu_bar_dock = w;
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	gtk_container_add(GTK_CONTAINER(parent2), menu_bar);

	/* Attach accel group to toplevel window */
	if((v->toplevel != NULL) &&
	   (accelgrp != NULL)
	)
	{
	    gtk_window_add_accel_group(
		GTK_WINDOW(v->toplevel), accelgrp
	    );
	}
}

/*
 *      Creates the tool bar for the viewer, this function should be
 *      called by ViewerNew(). 
 *
 *      Given parent is assumed to be a vbox.
 */
static void ViewerCreateToolBar(viewer_struct *v, GtkWidget *parent)
{
	gboolean show_tooltips = TRUE;
	GtkWidget *w, *parent2, *parent3;
	gint bw = 25, bh = 25;
	gint sw = 5, sh = -1;


	/* Handle for tool bar hbox */
	w = gtk_handle_box_new();
	v->tool_bar_dock = w;
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Tool bar hbox inside handle */
	w = gtk_hbox_new(FALSE, 0);
	v->tool_bar = w;
	gtk_container_border_width(GTK_CONTAINER(w), 2);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent3 = w;

	/* Open */
	w = (GtkWidget *)GUIButtonPixmapLabelV(
	    (guint8 **)icon_open_20x20_xpm,
	    "Open", NULL
	);
	v->open_btn = w;
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, bw, bh);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerOpenCB),
	    (gpointer)v
	);
	if(show_tooltips)
	    GUISetWidgetTip(w, VIEWER_TT_OPEN);
	gtk_widget_show(w);

	/* Clear */
	w = (GtkWidget *)GUIButtonPixmapLabelV(
	    (guint8 **)icon_clear_20x20_xpm,
	    "Clear", NULL
	);
	v->clear_btn = w;
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, bw, bh);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerClearCB),
	    (gpointer)v
	);
	if(show_tooltips)
	    GUISetWidgetTip(w, VIEWER_TT_CLEAR);
	gtk_widget_show(w);

	/* Separator */
	w = gtk_label_new("");
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, sw, sh);
	gtk_widget_show(w);

	/* Stop */
	w = (GtkWidget *)GUIButtonPixmapLabelV(
	    (guint8 **)icon_stop_20x20_xpm,
	    "Stop", NULL
	);
	v->stop_btn = w;
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, bw, bh);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerStopCB),
	    (gpointer)v
	);
	if(show_tooltips)
	    GUISetWidgetTip(w, VIEWER_TT_STOP);
	gtk_widget_show(w);

	return;
}

/*
 *	Creates the status bar for the viewer, this function should be
 *	called by ViewerNew().
 *
 *	Given parent is assumed to be a vbox.
 */
static void ViewerCreateStatusBar(
	viewer_struct *v, GtkWidget *parent
)
{
	gboolean show_tooltips = TRUE;
	GtkWidget *w, *parent2, *parent3;
	GtkAdjustment *adj;


	/* Main hbox for status bar and window map buttons */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent = w;

	/* Handle for status bar */
	w = gtk_handle_box_new();
	v->status_bar_dock = w;
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Toplevel hbox inside dock */
	w = gtk_hbox_new(FALSE, 0);
	v->status_bar_toplevel = w;
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;

	/* Main frame in toplevel */
	w = gtk_frame_new(NULL);
	gtk_widget_set_usize(w, -1, 25);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Table, put into main frame */
	w = gtk_table_new(1, 3, FALSE);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;


	/* Progress bar */
	adj = (GtkAdjustment *)gtk_adjustment_new(0, 1, 100, 0, 0, 0);
	w = gtk_progress_bar_new_with_adjustment(adj);
	v->status_bar_progress = w;
	gtk_widget_set_usize(w, 100, -1);
	gtk_progress_bar_set_orientation(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_LEFT_TO_RIGHT
	);
	gtk_progress_bar_set_bar_style(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_CONTINUOUS
	);
/*
	gtk_progress_bar_set_discrete_blocks(
	    GTK_PROGRESS_BAR(w), 10
	);
 */
	gtk_progress_set_activity_mode(
	    GTK_PROGRESS(w), FALSE
	);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 0, 1,
	    0,
	    GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);


	/* Label */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_container_border_width(GTK_CONTAINER(w), 1);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    1, 2, 0, 1,
	    GTK_SHRINK | GTK_EXPAND | GTK_FILL,
	    GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_label_new("");
	v->status_bar_label = w;
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 2);
	gtk_widget_show(w);


	/* Cursor position label */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_container_border_width(GTK_CONTAINER(w), 1);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    2, 3, 0, 1,
	    GTK_SHRINK | GTK_FILL,
	    GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_label_new("");
	v->status_bar_cursor_label = w;
	gtk_widget_set_usize(w, 100, -1);
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 2);
	gtk_widget_show(w);



	/* Handle bar for window map buttons */
	w = gtk_handle_box_new();
/*      v->window_map_dock = w; */
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Main frame in toplevel */
	w = gtk_frame_new(NULL);
	gtk_widget_set_usize(w, -1, 25);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;

	/* Hbox for window map buttons */
	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;

	/* Window map buttons */
	w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)manedit_16x16_xpm
	);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, 25, -1);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(MEditEditorNewCB),
	    (gpointer)v->core
	);
	if(show_tooltips)
	    GUISetWidgetTip(w, MEDIT_SB_TT_NEW_EDITOR);
	gtk_widget_show(w);

	w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)mp_viewer_16x16_xpm
	);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, 25, -1);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(MEditViewerNewCB),
	    (gpointer)v->core
	);
	if(show_tooltips)
	    GUISetWidgetTip(w, MEDIT_SB_TT_NEW_VIEWER);
	gtk_widget_show(w);


	return;
}

/*
 *	Creates a new viewer structure with its values reset and widgets
 *	built or NULL on error.
 */
viewer_struct *ViewerNew(gpointer core_ptr, gint editor_num)
{
	const gint	border_minor = 2,
			border_major = 5;
	gboolean show_tooltips = TRUE;
	guint accel_key, accel_mods;
	gchar *title[4];
	const gchar *label;
	guint8 **icon;
	gpointer combo_rtn, mclient_data;
	GdkColormap *colormap;
	GtkStyle *style;
	GtkAccelGroup *accelgrp;
	GtkWidget	*w, *fw, *menu,
			*parent, *parent2, *parent3, *parent4,
			*parent5, *scroll_parent;
	GtkCList *clist;
	void (*func_cb)(GtkWidget *w, gpointer);
	pulist_struct *pulist;
	pulistbox_struct *pulistbox;
	medit_cursors_list_struct *cursors_list = NULL;
	medit_styles_list_struct *styles_list = NULL;
	pref_struct *pref = NULL;
	medit_core_struct *core = MEDIT_CORE(core_ptr);
	const GtkTargetEntry dnd_tar_types[] = {
{"text/plain",			0,		MEDIT_DND_TYPE_INFO_TEXT_PLAIN},
{"text/uri-list",		0,		MEDIT_DND_TYPE_INFO_TEXT_URI_LIST},
{"STRING",			0,		MEDIT_DND_TYPE_INFO_STRING},
{MEDIT_DND_TARGET_EDITOR_BRANCH, GTK_TARGET_SAME_APP, MEDIT_DND_TYPE_INFO_EDITOR_BRANCH_CMD},
{MEDIT_DND_TARGET_VIEWER_BRANCH, GTK_TARGET_SAME_APP, MEDIT_DND_TYPE_INFO_VIEWER_BRANCH_CMD}
	};
	const GtkTargetEntry dnd_src_types[] = {
{MEDIT_DND_TARGET_VIEWER_BRANCH, GTK_TARGET_SAME_APP, MEDIT_DND_TYPE_INFO_VIEWER_BRANCH_CMD}
	};
	viewer_struct *v = VIEWER(g_malloc0(
	    sizeof(viewer_struct)
	));
	if(v == NULL)
	    return(NULL);

	if(core == NULL)
	{
	    g_free(v);
	    return(NULL);
	}

	cursors_list = &core->cursors_list;
	styles_list = &core->styles_list;
	pref = core->pref;

	v->freeze_count = 0;
	v->busy_count = 0;
	v->stop_count = 0;
	v->processing = FALSE;
	v->core = core;
	v->editor_num = editor_num;
	v->current_page = ViewerPageNumView;
	v->selected_index_branch = NULL;
	v->cur_manpage_path = NULL;
	v->cur_manpage_name = NULL;
	v->last_open_path = NULL;

#define DO_ADD_MENU_ITEM_LABEL {		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,	\
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_ITEM_SUBMENU {		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SUBMENU, accelgrp,	\
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  mclient_data, func_cb				\
 );						\
 GUIMenuItemSetSubMenu(w, submenu);		\
}
#define DO_ADD_MENU_ITEM_CHECK {		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,	\
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_SEP {			\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}

	/* Toplevel */
	v->toplevel = w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_widget_realize(w);
	GUISetWMIcon(w->window, (guint8 **)mp_viewer_48x48_xpm);
	if(PrefParmGetValueB(pref, MEDIT_PREF_PARM_RECORD_WIN_POS))
	{
	    /* TRUE implies pref is also valid */
	    gtk_widget_set_usize(
		w,
		MAX(pref->last_viewer_width, 256),
		MAX(pref->last_viewer_height, 256)
	    );
	}
	else
	{
	    gtk_widget_set_usize(
		w,
	        MEDIT_VIEWER_DEF_WIDTH, MEDIT_VIEWER_DEF_HEIGHT
	    );
	}
	gtk_window_set_policy(
	    GTK_WINDOW(w), 
	    TRUE, TRUE, FALSE
	);
	if((pref != NULL) && !GTK_WIDGET_NO_WINDOW(w))
	{
	    GdkGeometry geometry;

	    geometry.min_width = 256;
	    geometry.min_height = 256;

	    geometry.base_width = 0;
	    geometry.base_height = 0;

	    geometry.width_inc = 1;
	    geometry.height_inc = 1;
/*
	    geometry.min_aspect = 1.3;
	    geometry.max_aspect = 1.3;
 */
	    gdk_window_set_geometry_hints(
		w->window,
		&geometry,
		GDK_HINT_MIN_SIZE |
		GDK_HINT_BASE_SIZE |
		/* GDK_HINT_ASPECT | */
		GDK_HINT_RESIZE_INC
	    );
	}
	gtk_widget_add_events(
	    w,
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(ViewerCloseCB), v
	);
	gtk_window_set_title(GTK_WINDOW(w), MEDIT_VIEWER_TITLE);
	parent = w;

	/* Main GtkVBox */
	v->main_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;


	/* Create menu bar */
	ViewerCreateMenuBar(v, v->main_vbox);

	/* Create tool bar */
	ViewerCreateToolBar(v, v->main_vbox);


	/* Main notebook */
	w = gtk_notebook_new();
	v->main_notebook = w;
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(w), GTK_POS_TOP);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 2);
	gtk_notebook_set_scrollable(GTK_NOTEBOOK(w), TRUE);
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(w), TRUE);
	gtk_notebook_set_show_border(GTK_NOTEBOOK(w), TRUE);
/*      gtk_notebook_set_page(GTK_NOTEBOOK(w), 0); */
	gtk_signal_connect(
	    GTK_OBJECT(w), "switch_page",
	    GTK_SIGNAL_FUNC(ViewerSwitchPageCB),
	    (gpointer)v
	);
	gtk_widget_show(w);
	parent2 = w;


	/* Begin creating page 0 on main_notebook */

	w = gtk_vbox_new(FALSE, border_major);
	gtk_container_set_border_width(GTK_CONTAINER(w), border_major);
	gtk_notebook_append_page( 
	    GTK_NOTEBOOK(parent2),
	    w,
	    gtk_label_new("Manual Page")
	);
	gtk_widget_show(w);
	parent3 = w;

	/* Section and Manpage GtkHBox */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	/* Section GtkHBox */
	w = gtk_hbox_new(FALSE, border_minor);
        gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_widget_show(w);
        parent5 = w;

	w = gtk_label_new("Section:");
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
        gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Section PopUp List Box */
	v->section_pulistbox = pulistbox = PUListBoxNew(
	    parent5,
	    140, -1
	);
	pulist = PUListBoxGetPUList(pulistbox);
	if(show_tooltips)
	    GUISetWidgetTip(pulistbox->da, VIEWER_TT_SECTION_COMBO);
	PUListBoxMap(pulistbox);

#define PULIST_APPEND(_s_)	{		\
 const gint i = PUListAddItem(pulist, (_s_));	\
 PUListSetItemDataFull(				\
  pulist, i,					\
  STRDUP(_s_), (GtkDestroyNotify)g_free		\
 );						\
}
	PULIST_APPEND(MEDIT_SECT_NAME_ANY);
	PULIST_APPEND(MEDIT_SECT_NAME_EXACT);
	PULIST_APPEND(MEDIT_SECT_NAME_1);
	PULIST_APPEND(MEDIT_SECT_NAME_2);
	PULIST_APPEND(MEDIT_SECT_NAME_3);
	PULIST_APPEND(MEDIT_SECT_NAME_4);
	PULIST_APPEND(MEDIT_SECT_NAME_5);
	PULIST_APPEND(MEDIT_SECT_NAME_6);
	PULIST_APPEND(MEDIT_SECT_NAME_7);
	PULIST_APPEND(MEDIT_SECT_NAME_8);

#undef PULIST_APPEND

	PUListBoxSetLinesVisible(
	    pulistbox,
	    MIN(PUListGetTotalItems(pulist), 10)
	);

	/* Manpage GtkHBox */
	w = gtk_hbox_new(FALSE, border_minor);
        gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
        gtk_widget_show(w);
        parent5 = w;

	w = GUICreateMenuItemIcon(
	    (guint8 **)icon_manual_opened_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	w = GUIComboCreate(
	    "ManPage:",
	    "",			/* Initial value */
	    NULL,		/* Initial glist of items */
	    20,			/* Max items */
	    &combo_rtn,
	    (gpointer)v,
	    ViewerManPageActivateCB,
	    NULL
	);
	v->manpage_combo = (GtkWidget *)combo_rtn;
	gtk_combo_set_case_sensitive(
	    (GtkCombo *)combo_rtn,
	    TRUE
	);
	if(show_tooltips)  
	    GUISetWidgetTip(GTK_COMBO(combo_rtn)->entry, VIEWER_TT_SEARCH_MANPAGE);
	gtk_box_pack_start(GTK_BOX(parent5), w, TRUE, TRUE, 0);
	gtk_widget_show(w);


	/* Find GtkHBox */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	w = gtk_hbox_new(FALSE, border_minor);
        gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
        gtk_widget_show(w);
        parent5 = w;

	w = GUICreateMenuItemIcon(
	    (guint8 **)icon_search_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	w = GUIComboCreate(
	    "Find:",
	    "",                 /* Initial value */
	    NULL,               /* Initial glist of items */
	    20,                 /* Max items */
	    &combo_rtn,
	    (gpointer)v,
	    ViewerFindActivateCB,
	    NULL
	);
	v->find_combo = (GtkWidget *)combo_rtn;
	gtk_combo_set_case_sensitive(
	    (GtkCombo *)combo_rtn,
	    TRUE
	);
	gtk_box_pack_start(GTK_BOX(parent5), w, TRUE, TRUE, 0);
	gtk_widget_show(w);



	/* View text table parent to hold view text and scroll bars */
	w = gtk_table_new(2, 2, FALSE);
	gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent4 = w;

	/* View text */
	v->view_text = w = gtk_text_new(NULL, NULL);
	gtk_widget_set_events(
	    w,
	    GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK |
	    GDK_KEY_RELEASE_MASK
	);
	/* Need to connect key event signals to change editable
	 * state. Reason explained in ViewerViewKeyEventCB().
	 */
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(ViewerViewKeyEventCB), v
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(ViewerViewKeyEventCB), v
	);
	/* Need to connect button_press_event signal_after just
	 * after text widget is created, this is so that the right
	 * click menu can be mapped properly.
	 */
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(ViewerViewButtonEventCB), v
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(ViewerViewButtonEventCB), v
	);
	gtk_editable_set_editable(GTK_EDITABLE(w), FALSE);
	gtk_text_set_word_wrap(GTK_TEXT(w), TRUE);
	gtk_table_attach(
	    GTK_TABLE(parent4), w,
	    0, 1, 0, 1,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "changed",
	    GTK_SIGNAL_FUNC(ViewerTextChangeCB),
	    (gpointer)v
	);
	gtk_widget_realize(w);
	if(cursors_list != NULL)  
	    gdk_window_set_cursor(w->window, cursors_list->text);
	/* Set up DND target for the view text */
	GUIDNDSetTar(
	    w,
	    dnd_tar_types,
	    sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
	    GDK_ACTION_COPY | GDK_ACTION_MOVE,  /* Actions */
	    GDK_ACTION_MOVE,                    /* Default action if same */
	    GDK_ACTION_COPY,                    /* Default action */
	    ViewerViewTextDNDDataRecievedCB, v
	);

	/* Create a new style for the GtkText, because we may want to
	 * adjust the style later on.
	 */
	if(GTK_WIDGET_NO_WINDOW(w))
	    colormap = NULL;
	else
	    colormap = gdk_window_get_colormap(w->window);
	style = gtk_widget_get_style(w);
	style = (style != NULL) ?
	    gtk_style_copy(style) : NULL;
	if(style != NULL)
	{
	    GtkStyle *style_src_ptr;

	    /* Set base color */
	    style_src_ptr = styles_list->manpage_text_background;
	    if((style_src_ptr != NULL) && (colormap != NULL))
	    {
		style->base[GTK_STATE_NORMAL] =
		    style_src_ptr->base[GTK_STATE_NORMAL];
		gdk_color_alloc(
		    colormap,
		    &style->base[GTK_STATE_NORMAL]
		);
	    }

	    style_src_ptr = styles_list->manpage_text_standard;
	    if(style_src_ptr != NULL)
	    {
		if(style->font != NULL)
		    gdk_font_unref(style->font);
		style->font = style_src_ptr->font;
		if(style->font != NULL)
		    gdk_font_ref(style->font);
	    }

	    gtk_widget_set_style(w, style);
	}
	gtk_widget_show(w);

	/* Vertical scroll bar for text widget */
	scroll_parent = gtk_vscrollbar_new(GTK_TEXT(w)->vadj);
	gtk_table_attach(
	    GTK_TABLE(parent4), scroll_parent,
	    1, 2, 0, 1,
	    GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(scroll_parent);


	/* View text right click menu */
	menu = (GtkWidget *)GUIMenuCreate();
	v->view_menu = menu;
	accelgrp = NULL;
	mclient_data = v;

	if(menu != NULL)
	{
	    icon = (guint8 **)icon_copy_20x20_xpm;
	    label = "Copy";
	    accel_key = GDK_c;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = ViewerCopyCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->view_copy_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Select All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerSelectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->view_select_all_mi = w;

	    icon = NULL;
	    label = "Unselect All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerUnselectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->view_unselect_all_mi = w;
	}


	/* Begin creating page 1 on main_notebook */

	/* Index ctree vbox parent to hold index ctree widgets */
	w = gtk_vbox_new(FALSE, 5);
	gtk_container_border_width(GTK_CONTAINER(w), 5);
	gtk_notebook_append_page(
	    GTK_NOTEBOOK(parent2),
	    w,
	    gtk_label_new("Index")
	);
	gtk_widget_show(w);
	parent3 = w;


	/* Hbox for find in pages combo */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	w = GUICreateMenuItemIcon(
	    (guint8 **)icon_search_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	w = GUIComboCreate(
	    "Find Content:",
	    "",                 /* Initial value */
	    NULL,               /* Initial glist of items */
	    20,                 /* Max items */
	    &combo_rtn,
	    v,
	    ViewerFindInPagesActivateCB,
	    NULL
	);
	v->index_find_in_pages_combo = (GtkWidget *)combo_rtn;
	gtk_combo_set_case_sensitive(
	    (GtkCombo *)combo_rtn,
	    TRUE
	);
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
	gtk_widget_show(w);


	/* Scrolled window for index ctree */
	w = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC,
	    GTK_POLICY_AUTOMATIC
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	scroll_parent = w;

	/* Index ctree */
	title[0] = "Name";
	title[1] = "Lines";
	title[2] = "Read";
	title[3] = "Description";
	v->index_ctree = w = gtk_ctree_new_with_titles(
	    4, 0, title
	);
	clist = GTK_CLIST(w);
	if(!GTK_WIDGET_NO_WINDOW(w))
	{
	    gtk_signal_connect_after(
		GTK_OBJECT(w), "button_press_event",
		GTK_SIGNAL_FUNC(ViewerMenuMapCB),
		(gpointer)v
	    );
	}
	gtk_clist_column_titles_passive(clist);
	gtk_clist_column_titles_show(clist);
	gtk_clist_set_row_height(clist, MEDIT_LIST_ROW_SPACING);
	gtk_clist_set_shadow_type(clist, GTK_SHADOW_IN);
	gtk_clist_set_column_width(clist, 0, 200);
	gtk_clist_set_column_width(clist, 1, 60);
	gtk_clist_set_column_width(clist, 2, 40);
	gtk_clist_set_column_width(clist, 3, 250);
	gtk_container_add(GTK_CONTAINER(scroll_parent), w);
	gtk_signal_connect(
	    GTK_OBJECT(w), "tree_select_row",
	    GTK_SIGNAL_FUNC(ViewerIndexCTreeSelectCB),
	    (gpointer)v
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "tree_unselect_row",
	    GTK_SIGNAL_FUNC(ViewerIndexCTreeUnselectCB),
	    (gpointer)v
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "tree_expand",
	    GTK_SIGNAL_FUNC(ViewerIndexCTreeExpandCB),
	    (gpointer)v
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "tree_collapse",
	    GTK_SIGNAL_FUNC(ViewerIndexCTreeExpandCB), 
	    (gpointer)v
	);
	/* Set up DND for the index ctree */
	GUIDNDSetSrc(
	    w,
	    dnd_src_types,
	    sizeof(dnd_src_types) / sizeof(GtkTargetEntry),
	    GDK_ACTION_COPY,			/* Actions */
	    GDK_BUTTON1_MASK,			/* Buttons */
	    NULL,
	    ViewerIndexCTreeDNDDataRequestCB,
	    ViewerDNDDataDeleteCB,
	    NULL,
	    v
	);
	/* No drag target for this index ctree */
	gtk_widget_show(w);

	/* Index ctree right click menu */
	menu = (GtkWidget *)GUIMenuCreate();
	v->index_menu = menu;
	accelgrp = NULL;
	mclient_data = v;

	if(menu != NULL)
	{
	    icon = NULL;
	    label = "Expand/Collapse";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerIndexBranchExpandCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->index_expand_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_folder_parent_20x20_xpm;
	    label = "Parent";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerIndexGotoParentCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->index_parent_mi = w;

	    icon = (guint8 **)icon_goto_20x20_xpm;
	    label = "Goto";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerGotoCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->index_goto_mi = w;

	    icon = (guint8 **)icon_reload_20x20_xpm;
	    label = "Refresh";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerIndexRefreshCB;
	    DO_ADD_MENU_ITEM_LABEL
	    v->index_refresh_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_stop_20x20_xpm;
	    label = "Stop";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ViewerStopCB; 
	    DO_ADD_MENU_ITEM_LABEL
	    v->index_stop_mi = w;
	}

	/* Separator */
	w = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Hbox for index ctree buttons */
	w = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 5);
	gtk_widget_show(w);
	parent4 = w;

	/* Parent button */
	w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_folder_parent_20x20_xpm, "Parent", NULL
	);
	v->index_parent_btn = w;
	gtk_widget_set_usize(w, VIEWER_BTN_WIDTH, VIEWER_BTN_HEIGHT);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerIndexGotoParentCB), v
	);
	gtk_widget_show(w);

	/* Goto button */
	w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_goto_20x20_xpm, "Goto", NULL
	);
	v->index_goto_btn = w;
	gtk_widget_set_usize(w, VIEWER_BTN_WIDTH, VIEWER_BTN_HEIGHT);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerGotoCB), v
	);
	gtk_widget_show(w);

	/* Refresh button */
	w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_reload_20x20_xpm, "Refresh", NULL
	);
	v->index_refresh_btn = w;
	gtk_widget_set_usize(w, VIEWER_BTN_WIDTH, VIEWER_BTN_HEIGHT);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ViewerIndexRefreshCB), v
	);
	gtk_widget_show(w);






	/* Create status bar */
	ViewerCreateStatusBar(v, v->main_vbox);




#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_SUBMENU
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP

	/* Reset values to defaults */
	ViewerReset(v, FALSE);

	/* Update menus */
	ViewerUpdateMenus(v);

	return(v);
}

/*
 *	Updates all menus on viewer.
 */
void ViewerUpdateMenus(viewer_struct *v)
{
	gboolean sensitive, state;
	gint toolbar_btn_layout = 2;
	GtkWidget *w, *menu;
	GtkCTreeRow *branch_row;
	medit_core_struct *core;
	viewer_index_item_struct *item;

	if(v == NULL)
	    return;

	core = v->core;

	v->freeze_count++;

#define SET_WIDGET_SENSITIVITY		{	\
 if(w != NULL)					\
  gtk_widget_set_sensitive(w, sensitive);	\
}
#define SET_CHECK_MENU_ITEM_STATE	{	\
 GUIMenuItemSetCheck(w, state, TRUE);		\
}
#define SET_BUTTON_LAYOUT		{	\
 if(w != NULL) {				\
  switch(toolbar_btn_layout) {			\
   case 2:					\
    GUIButtonChangeLayout(w, 1, 1);		\
    gtk_widget_set_usize(w, 60, 50);		\
    break;					\
   case 1:					\
    GUIButtonChangeLayout(w, 1, 0);		\
    gtk_widget_set_usize(w, 30, 30);		\
    break;					\
   default:					\
    GUIButtonChangeLayout(w, 0, 1);		\
    gtk_widget_set_usize(w, -1, 30);		\
    break;					\
} } }

	/* Get tool bar button display type */
	if(PrefParmGetValueB(
	    core->pref, MEDIT_PREF_PARM_TOOLBAR_PT
	))
	    toolbar_btn_layout = 2;
	else if(PrefParmGetValueB(
	    core->pref, MEDIT_PREF_PARM_TOOLBAR_P
	))          
	    toolbar_btn_layout = 1;
	else
	    toolbar_btn_layout = 0;


/* TODO: Update title */


	/* File menu */
	menu = v->file_mh;
	if(menu != NULL)
	{
	    sensitive = TRUE;
	    gtk_widget_set_sensitive(v->open_mi, sensitive);
	}

	/* Edit menu */
	menu = v->edit_mh;
	if(menu != NULL)
	{
	    sensitive = TRUE;
	    gtk_widget_set_sensitive(v->edit_copy_mi, sensitive);
	    gtk_widget_set_sensitive(v->edit_select_all_mi, sensitive);
	    gtk_widget_set_sensitive(v->edit_unselect_all_mi, sensitive);
	    gtk_widget_set_sensitive(v->edit_preferences_mi, sensitive);
	}

	/* View menu */
	menu = v->view_mh;
	if(menu != NULL)
	{
	    w = v->view_view_mi;
	    state = (v->current_page == ViewerPageNumView) ? TRUE : FALSE;
	    SET_CHECK_MENU_ITEM_STATE

	    w = v->view_index_mi;
	    state = (v->current_page == ViewerPageNumIndex) ? TRUE : FALSE;
	    SET_CHECK_MENU_ITEM_STATE
	}


	/* View popup menu */
	menu = v->view_menu;
	if(menu != NULL)
	{
	    sensitive = TRUE;
	    gtk_widget_set_sensitive(v->view_copy_mi, sensitive);
	    gtk_widget_set_sensitive(v->view_select_all_mi, sensitive);
	    gtk_widget_set_sensitive(v->view_unselect_all_mi, sensitive);
	}

	/* Index right click menu */
	menu = v->index_menu;
	if(menu != NULL)
	{
	    if(v->selected_index_branch == NULL)
		branch_row = NULL;
	    else
		branch_row = GTK_CTREE_ROW(v->selected_index_branch);
	    w = v->index_parent_mi;
	    sensitive = (branch_row != NULL) ?
		(branch_row->parent != NULL) : FALSE;
	    SET_WIDGET_SENSITIVITY  

	    if((v->index_ctree != NULL) &&
	       (v->selected_index_branch != NULL)
	    )
		item = VIEWER_INDEX_ITEM(gtk_ctree_node_get_row_data(
		    GTK_CTREE(v->index_ctree), v->selected_index_branch
		));
	    else
		item = NULL;
	    w = v->index_expand_mi;
	    if(item == NULL)
		sensitive = FALSE;
	    else if(item->type == ViewerIndexItemTypeDirectory)
		sensitive = TRUE;
	    else
		sensitive = FALSE;
	    SET_WIDGET_SENSITIVITY

	    w = v->index_goto_mi;
	    if(item == NULL)
		sensitive = FALSE;
	    else if((item->type == ViewerIndexItemTypeManualPage) ||
		    (item->type == ViewerIndexItemTypeOtherFile)
	    )
		sensitive = TRUE;
	    else
		sensitive = FALSE;
	    SET_WIDGET_SENSITIVITY

	    w = v->index_refresh_mi;
	    sensitive = TRUE;
	    SET_WIDGET_SENSITIVITY

	    w = v->index_stop_mi;
	    sensitive = v->processing;
	    SET_WIDGET_SENSITIVITY
	}

	/* Buttons on tool bar */
	w = v->tool_bar;
	if(w != NULL)
	{
	    w = v->open_btn;
	    sensitive = !FileBrowserIsQuery();
	    SET_WIDGET_SENSITIVITY
	    SET_BUTTON_LAYOUT

	    w = v->clear_btn;
	    SET_BUTTON_LAYOUT

	    w = v->stop_btn;
	    sensitive = v->processing;
	    SET_WIDGET_SENSITIVITY
	    SET_BUTTON_LAYOUT
	}


	/* Buttons on index ctree page */
	if(v->selected_index_branch == NULL)
	    branch_row = NULL;
	else
	    branch_row = GTK_CTREE_ROW(v->selected_index_branch);
	w = v->index_parent_btn;
	sensitive = ((branch_row == NULL) ?
	    FALSE : (branch_row->parent != NULL)
	);
	SET_WIDGET_SENSITIVITY

	if((v->index_ctree != NULL) &&
	   (v->selected_index_branch != NULL)
	)
	    item = VIEWER_INDEX_ITEM(gtk_ctree_node_get_row_data(
		GTK_CTREE(v->index_ctree), v->selected_index_branch
	    ));
	else
	    item = NULL;
	w = v->index_goto_btn;
	if(item == NULL)
	    sensitive = FALSE;
	else if((item->type == ViewerIndexItemTypeManualPage) ||
		(item->type == ViewerIndexItemTypeOtherFile)
	)
	    sensitive = TRUE;
	else
	    sensitive = FALSE;
	SET_WIDGET_SENSITIVITY 

	w = v->index_refresh_btn;
	sensitive = TRUE;
	SET_WIDGET_SENSITIVITY


	v->freeze_count--;

#undef SET_WIDGET_SENSITIVITY
#undef SET_CHECK_MENU_ITEM_STATE
#undef SET_BUTTON_LAYOUT
}



/*
 *	Updates the cursor position value on the status bar.
 */
void ViewerSetStatusPosition(viewer_struct *v, gint row, gint column)
{
	gchar *s;
	GtkWidget *w;

	if(v == NULL)
	    return;

	w = v->status_bar_cursor_label;
	if(w == NULL)
	    return;

	s = g_strdup_printf(
	    "Row: %i / Col: %i",
	    row, column
	);
	gtk_label_set_text(GTK_LABEL(w), s);
	g_free(s);

	while(gtk_events_pending() > 0)
	    gtk_main_iteration();
}

/*
 *	Sets the status message for the viewer.
 *
 *	Passing NULL clears the message.
 *
 *      Note that gtk_main_iteration() will be called when this function
 *      is called to update the label properly.
 */
void ViewerSetStatusMessage(viewer_struct *v, const gchar *mesg)
{
	GtkWidget *w;

	if(v == NULL)
	    return;

	w = v->status_bar_label;
	if(w == NULL)
	    return;

	gtk_label_set_text(
	    GTK_LABEL(w),
	    (mesg != NULL) ? mesg : ""
	);

	while(gtk_events_pending() > 0)
	    gtk_main_iteration();
}

/*
 *	Sets the status of the progress bar on the viewer.
 *
 *	Value must be from 0.0 to 1.0 inclusive.
 *
 *	Note that gtk_main_iteration() will be called when this function
 *	is called to update the progress bar properly.
 */
void ViewerSetStatusProgress(viewer_struct *v, gfloat percent)
{
	gfloat p;
	GtkWidget *w;

	if(v == NULL)
	    return;

	w = v->status_bar_progress;
	if(w == NULL)
	    return;

	/* Negative? Implies just do activity */
	if(percent < 0.0)
	{
	    GtkAdjustment *adj = GTK_PROGRESS(w)->adjustment;
	
	    percent = gtk_progress_get_value(GTK_PROGRESS(w)) + 1;
	    if(percent > adj->upper)
		percent = adj->lower;

	    gtk_progress_set_activity_mode(
		GTK_PROGRESS(w), TRUE
	    );
	    gtk_progress_set_value(GTK_PROGRESS(w), percent);

	    while(gtk_events_pending() > 0)
		gtk_main_iteration();

	    return;
	}

	if(percent > 1.0f)
	    percent = 1.0f;

	gtk_progress_set_format_string(
	    GTK_PROGRESS(w), "%p%%"
	);
	gtk_progress_set_show_text(
	    GTK_PROGRESS(w), (percent > 0.0) ? TRUE : FALSE
	);
	gtk_progress_set_activity_mode(
	    GTK_PROGRESS(w), FALSE
	);

	p = v->status_bar_progress_pos_last;
	if(p > percent)
	    p = percent;

	while(TRUE)
	{
	    if(p > percent)
		p = percent;

	    gtk_progress_bar_update(
		GTK_PROGRESS_BAR(w), p
	    );

	    while(gtk_events_pending() > 0)
		gtk_main_iteration();

	    if(p < percent)
		p += 0.05f;
	    else
		break;
	}

	v->status_bar_progress_pos_last = percent;
}

/*
 *      Records positions and sizes of viewer's widgets onto its
 *      core structure's preferences structure.
 */
void ViewerRecordPositions(viewer_struct *v)
{
	medit_core_struct *core;
	pref_struct *pref;
	GtkWidget *w;

	if(v == NULL)
	    return;

	core = v->core;
	pref = core->pref;

	w = v->toplevel;
	if(w != NULL)
	{
	    GdkWindow *window = w->window;
	    if(window != NULL)
	    {
		gint x, y, width, height, depth;

		gdk_window_get_geometry(
		    window, &x, &y, &width, &height, &depth
		);

		pref->last_viewer_width = MAX(width, 0);
		pref->last_viewer_height = MAX(height, 0);
	    }

	    if(v->last_open_path != NULL)
	    {
		g_free(pref->last_opened_manage_view_path);
		pref->last_opened_manage_view_path = STRDUP(
		    v->last_open_path
		);
	    }
	}
}

/*
 *	Resets all values to defaults on viewer and deletes
 *	undo/redo list and all layout branches.
 */
void ViewerReset(viewer_struct *v, const gboolean unmap)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch, *prev_branch;
	GtkCTreeRow *branch_row;
	pref_struct *pref;
	GtkWidget *w;
	medit_core_struct *core;

	if(v == NULL)
	    return;

	core = v->core;
	pref = core->pref;

	if(unmap)
	    gtk_widget_hide(v->toplevel);

	v->freeze_count = 0;
	v->busy_count = 0;
	v->stop_count = 0;
	v->processing = FALSE;

	v->status_bar_progress_pos_last = 0;

	v->last_scroll_hpos = 0.0f;
	v->last_scroll_vpos = 0.0f;

	/* Resize toplevel */
	w = v->toplevel;
	if(PrefParmGetValueB(pref, MEDIT_PREF_PARM_RECORD_WIN_POS))
	{
	    /* TRUE implies pref is also valid */
	    gtk_widget_set_usize(
		w,
		MAX(pref->last_viewer_width, 256),
		MAX(pref->last_viewer_height, 256)
	    );
	}
	else
	{
	    gtk_widget_set_usize(
		w,
		MEDIT_VIEWER_DEF_WIDTH, MEDIT_VIEWER_DEF_HEIGHT
	    );
	}


	/* Clear the search combo */
	GUIComboClearAll(v->manpage_combo);


	/* Remove all branches on index ctree */
	ctree = GTK_CTREE(v->index_ctree);
	branch = gtk_ctree_node_nth(ctree, 0);
	while(branch != NULL)  
	{
	    prev_branch = branch;	/* Record the 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);
	}

	/* Switch page */
	w = v->main_notebook;
	if(w != NULL)
	{
	    gtk_notebook_set_page(
		GTK_NOTEBOOK(w),
		ViewerPageNumView
	    );
	}
	v->current_page = ViewerPageNumView;


	/* Free current manual page file path */
	g_free(v->cur_manpage_path);
	v->cur_manpage_path = NULL;

	/* Free current manual page name */
	g_free(v->cur_manpage_name);
	v->cur_manpage_name = NULL;

	/* Get paths */
	if(pref != NULL)
	{
	    g_free(v->last_open_path);
	    v->last_open_path = STRDUP(pref->last_opened_manage_view_path);
	}

	/* Reset status messages */
	ViewerSetStatusPosition(v, 0, 0);
	ViewerSetStatusMessage(v, NULL);
	ViewerSetStatusProgress(v, 0.0f);

	/* Clear view text */
	ViewerTextDelete(v, 0, -1);
}

/*
 *	Maps viewer as needed.
 */
void ViewerMap(viewer_struct *v)
{
	if(v == NULL)
	    return;

	gtk_widget_show_raise(v->toplevel);
}

/*
 *      Unmaps viewer as needed.
 */
void ViewerUnmap(viewer_struct *v)
{       
	if(v == NULL)
	    return;

	gtk_widget_hide(v->toplevel);
}


/*
 *	Destroys the viewer and all its widgets and allocated resources.
 *
 *	The viewer structure itself will also be deleted.
 */
void ViewerDelete(viewer_struct *v)
{
	GtkCList *clist;
	GtkCTree *ctree;
	GtkCTreeNode *branch, *prev_branch;
	GtkCTreeRow *branch_row;

	if(v == NULL)
	    return;

	v->freeze_count++;

	gtk_widget_hide(v->toplevel);

	/* Clear the search combo */
	GUIComboClearAll(v->manpage_combo);

	/* Remove all branches on index ctree */
	ctree = GTK_CTREE(v->index_ctree);
	clist = GTK_CLIST(ctree);
	gtk_clist_freeze(clist);
	branch = gtk_ctree_node_nth(ctree, 0);
	while(branch != NULL)  
	{
	    prev_branch = branch;	/* Record the 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(clist);

	/* Clear the text */
	ViewerTextDelete(v, 0, -1);

	/* Clear the sections list */
	PUListClear(PUListBoxGetPUList(v->section_pulistbox));

	PUListBoxDelete(v->section_pulistbox);
	GTK_WIDGET_DESTROY(v->toplevel);

	v->freeze_count--;

	g_free(v->cur_manpage_path);
	g_free(v->cur_manpage_name);
	g_free(v->last_open_path);

	g_free(v);
}
