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

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

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

#include "editor.h"
#include "editorfip.h"
#include "editorundo.h"
#include "editorcb.h"
#include "editordnd.h"
#include "editorop.h"
#include "editorfio.h"
#include "pref.h"
#include "prefop.h"
#include "manedit.h"
#include "config.h"


void EditorItemDestroyCB(gpointer data);

gint EditorKeyEventCB(GtkWidget *widget, GdkEventKey *key, gpointer data);
gint EditorTextButtonEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
gint EditorCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void EditorCloseMCB(GtkWidget *widget, gpointer data);
void EditorCloseAllMCB(GtkWidget *widget, gpointer data);

void EditorManualNewCB(GtkWidget *widget, gpointer data);
void EditorManualOpenCB(GtkWidget *widget, gpointer data);
void EditorManualNewFromTemplateCB(GtkWidget *widget, gpointer data);
void EditorManualSaveCB(GtkWidget *widget, gpointer data);
void EditorManualSaveAsCB(GtkWidget *widget, gpointer data);
void EditorManualRevertCB(GtkWidget *widget, gpointer data);
void EditorManualCloseCB(GtkWidget *widget, gpointer data);

void EditorManualAddHeaderCB(GtkWidget *widget, gpointer data);
void EditorManualAddSectionCB(GtkWidget *widget, gpointer data);
void EditorManualRemoveSectionCB(GtkWidget *widget, gpointer data);
void EditorManualPropertiesCB(GtkWidget *widget, gpointer data);
void EditorManualPreviewCB(GtkWidget *widget, gpointer data);
void EditorManualSyntaxHighlightToggleCB(GtkWidget *widget, gpointer data);

void EditorUndoCB(GtkWidget *widget, gpointer data);
void EditorRedoCB(GtkWidget *widget, gpointer data);
void EditorCutCB(GtkWidget *widget, gpointer data);
void EditorCopyCB(GtkWidget *widget, gpointer data);
void EditorPasteCB(GtkWidget *widget, gpointer data);
void EditorDeleteCB(GtkWidget *widget, gpointer data);
void EditorSelectAllCB(GtkWidget *widget, gpointer data);
void EditorUnselectAllCB(GtkWidget *widget, gpointer data);
void EditorPreferencesCB(GtkWidget *widget, gpointer data);

void EditorFmtParagraphLeftCB(GtkWidget *widget, gpointer data);
void EditorFmtParagraphRightCB(GtkWidget *widget, gpointer data);
void EditorFmtParagraphIndentedCB(GtkWidget *widget, gpointer data);
void EditorFmtListItemCB(GtkWidget *widget, gpointer data);
void EditorFmtBoldCB(GtkWidget *widget, gpointer data);
void EditorFmtUnderlineCB(GtkWidget *widget, gpointer data);
void EditorFmtLineBreakCB(GtkWidget *widget, gpointer data);
void EditorFmtAmpersandCB(GtkWidget *widget, gpointer data);
void EditorFmtLessThanCB(GtkWidget *widget, gpointer data);
void EditorFmtGreaterThanCB(GtkWidget *widget, gpointer data);
void EditorStripTagsCB(GtkWidget *widget, gpointer data);

void EditorMapFIPCB(GtkWidget *widget, gpointer data);

void EditorFindBarFindCB(GtkWidget *widget, gpointer data);
void EditorFindBarFindActivateCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceAllCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceEntirePageCB(GtkWidget *widget, gpointer data);
static int EditorFindBarReplaceEntirePageIterationCB(
	editor_struct *editor, GtkCTreeNode *branch,
	char *needle, const char *replacement,
	gboolean case_sensitive
);
void EditorFindBarReplaceActivateCB(GtkWidget *widget, gpointer data);

static void EditorDoFindCB(
	editor_struct *editor, GtkCombo *find_combo, gboolean case_sensitive
);
static void EditorDoReplaceCB(
	editor_struct *editor,
	GtkCombo *find_combo, GtkCombo *replace_combo,
	gboolean case_sensitive
);

static void EditorMenuMapPositionCB(
	GtkMenu *menu, gint *x, gint *y, gpointer data
);
gint EditorMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void EditorButtonMenuMapCB(GtkButton *button, gpointer data);
void EditorMenuHideCB(GtkWidget *widget, gpointer data);

void EditorLayoutCTreeDoExpandCB(GtkWidget *widget, gpointer data);
void EditorLayoutCTreeSelectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void EditorLayoutCTreeUnselectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void EditorLayoutCTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
);

gint EditorSyntaxHighlightTimeoutCB(gpointer data);

gint EditorTextKeyEventCB(
	GtkEditable *editable, GdkEventKey *key, gpointer data
);
void EditorTextChangeCB(GtkEditable *editable, gpointer data);
void EditorTextInsertCB(
	GtkEditable *editable, const gchar *text, gint length,
	gint *position, gpointer data
);
void EditorTextDeleteCB(
	GtkEditable *editable, gint start_pos, gint end_pos, 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)


/*
 *	Set to the pointer of the editor that is currently having
 *	an undo or redo operation done on it, this is to keep track 
 *	of and discard signals like "insert_text" and "delete_text"
 *	which would be sent if text was being reinserted or redeleted
 *	during and undo or redo process.
 */
static editor_struct *editor_processing_undo = NULL;


/*
 *	Destroys a branch's editor_item_struct.
 */
void EditorItemDestroyCB(gpointer data)
{
	gint i, j;
	editor_struct *editor;
	editor_item_struct *item = EDITOR_ITEM(data);
	if(item == NULL)
	    return;

	/* Get pointer to corresponding editor for this item */
	editor = item->editor;
	if(editor != NULL)
	{
	    GtkCTreeNode *branch = item->this_branch;
	    if(branch != NULL)
	    {
		/* Editor has this selected? */
		if(editor->selected_branch == branch)
		    editor->selected_branch = NULL;

		/* Remove this branch from the editor's layout_trunk list
		 * if this branch is found to be in that list
		 */
		for(i = 0; i < editor->total_layout_trunks; i++)
		{
		    if(editor->layout_trunk[i] == branch)
		    {
			editor->total_layout_trunks--;
			for(j = i; j < editor->total_layout_trunks; j++)
			    editor->layout_trunk[j] =
				editor->layout_trunk[j + 1];
			i--;	/* Back 1, incremented on next loop */
		    }
		}
		if(editor->total_layout_trunks < 0)
		    editor->total_layout_trunks = 0;
	    }
	}

	/* Delete the item and all its allocated resources */
	EditorItemDelete(item);
}

/*
 *	Editor toplevel window key press and release callback.
 */
gint EditorKeyEventCB(GtkWidget *widget, GdkEventKey *key, gpointer data)
{
	gint status = FALSE;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if((key == NULL) || (editor == NULL))
	    return(status);

	core = editor->core;

	/* Handle by event type */
	switch((gint)key->type)
	{
	  case GDK_KEY_PRESS:
	    break;

	  case GDK_KEY_RELEASE:
	    break;
	}

	return(status);
}

/*
 *	Text "button_press_event" or "button_release_event" signal
 *	callback.
 */
gint EditorTextButtonEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	GtkText *text;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (button == NULL) || (editor == 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(editor->edit_panel_menu),
		    NULL, NULL, NULL, editor,
		    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);
}

/*
 *	Close callback.
 */
gint EditorCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	gint i;
	gboolean yes_to_all = FALSE;
	GtkCTreeNode *branch;
	GtkWidget *toplevel;
	editor_item_struct *item;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return(TRUE);

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

	editor->freeze_count++;

	core = editor->core;
	toplevel = editor->toplevel;

	/* Go through each layout trunk's item data and check if
	 * it has changes. Only need to check the toplevel trunk
	 * branch node's data since it's has_changes member represents
	 * changes on child branches.
	 */
	for(i = 0; i < editor->total_layout_trunks; i++)
	{
	    branch = editor->layout_trunk[i];
	    if(branch == NULL)
		continue;

	    item = EditorBranchGetData(
		GTK_CTREE(editor->layout_ctree), branch
	    );
	    if(item == NULL)
		continue;

	    /* Item data not of type file? This would imply something
	     * is very wrong since toplevel trunk branches should always
	     * have item data of type EditorItemTypeFile
	     */
	    if(item->type != EditorItemTypeFile)
		continue;

	    if(item->has_changes)
	    {
		gint status;
		gchar *msg = g_strdup_printf(
		    "Save changes to manual page `%s'?",
		    item->name
		);
		if(yes_to_all)
		{
		    status = CDIALOG_RESPONSE_YES_TO_ALL;
		}
		else
		{
		    CDialogSetTransientFor(toplevel);
		    status = CDialogGetResponse(
"Save Changes",
			msg,
			NULL,
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		}
		g_free(msg);

		/* Check response */
		switch(status)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		  case CDIALOG_RESPONSE_OK:
		    /* Request to save, check if the full path is set.
		     * If the full path is not set (regardless of if the
		     * name is set or not, we need to consider that
		     * this file was never saved before and thus we need
		     * to request from the user a `save as'.
		     */
		    if(item->full_path == NULL)
		    {
			/* Save as */
			gboolean response;
			gchar **path_rtn;
			gint nftypes = 0, path_total_rtns;
			fb_type_struct **ftypes_list = NULL, *ftype_rtn = NULL;

			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(
			    "Save Manual Page As",
			    "Save", "Cancel",
			    editor->last_save_as_path,
			    ftypes_list, nftypes,
			    &path_rtn, &path_total_rtns, &ftype_rtn
			);
			FileBrowserSetTransientFor(NULL);
			FileBrowserDeleteTypeList(ftypes_list, nftypes);
			if(response && (path_total_rtns > 0))
			{
			    const gchar *path = path_rtn[0];
			    EditorFileSaveAs(editor, path, branch);
			    FileBrowserReset();
			}
			else
			{
			    /* User canceled save as, abort entire close
			     * procedure
			     */
			    editor->freeze_count--;
			    return(TRUE);
			}
		    }
		    else
		    {
			/* Save */
			EditorFileSave(editor, branch);
		    }
		    break;

		  case CDIALOG_RESPONSE_NO:
		    break;

		  case CDIALOG_RESPONSE_CANCEL:
		  case CDIALOG_RESPONSE_NOT_AVAILABLE:
		    /* Response not available, abort entire close
		     * procedure
		     */
		    editor->freeze_count--;
		    return(TRUE);
		    break;
		}
	    }
	}

/* Record values from editor back to core structure, like last
 * opened and save paths?
 */

	/* Record editor window positions */
	EditorRecordPositions(editor);

	/* Reset the editor and unmap it */
	EditorReset(editor, TRUE);

	editor->freeze_count--;

	return(TRUE);
}

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

/*
 *	Close all windows.
 */
void EditorCloseAllMCB(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;
}


/*
 *	New manual page callback.
 */
void EditorManualNewCB(GtkWidget *widget, gpointer data)
{
	gint i, n;
	GtkCTree *ctree;
	GtkCTreeNode *branch, *branch_insert, *new_branch;
	GtkCTreeRow *branch_row;
	GtkWidget *toplevel;
	gchar *text[1];
	medit_pixmaps_list_struct *pixmaps_list;
	medit_core_struct *core;
	editor_item_struct *item;
	gchar untitled_title[256];
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	core = editor->core;
	pixmaps_list = &core->pixmaps_list;

	/* Get selected branch */
	branch = editor->selected_branch;
	branch = EditorItemGetToplevel(editor, branch);
	if(branch == NULL)
	{
	    branch_insert = NULL;
	}
	else
	{
	    branch_row = GTK_CTREE_ROW(branch);
	    branch_insert = (branch_row != NULL) ?
		branch_row->sibling : NULL;
	}

	/* Now branch_insert should be the branch to insert before or
	 * NULL
	 */

	/* Allocate a new entry in the layout_trunk pointer array */
	if(editor->total_layout_trunks < 0)
	    editor->total_layout_trunks = 0;
	if(branch_insert == NULL)
	{
	    n = editor->total_layout_trunks;
	}
	else
	{
	    for(n = 0; n < editor->total_layout_trunks; n++)
	    {
		if(editor->layout_trunk[n] == branch_insert)
		    break;
	    }
	}
	if(n > editor->total_layout_trunks)
	    n = editor->total_layout_trunks;

	/* Variable n is now set to the index in the layout_trunk pointer
	 * array where we want to insert the new branch pointer
	 */

	/* Allocate a new pointer */
	editor->total_layout_trunks++;
	editor->layout_trunk = (GtkCTreeNode **)g_realloc(
	    editor->layout_trunk,
	    editor->total_layout_trunks * sizeof(GtkCTreeNode *)
	);
	if(editor->layout_trunk == NULL)
	{
	    editor->total_layout_trunks = 0;
	    editor->freeze_count--;
	    return;
	}

	/* Shift pointers */
	for(i = editor->total_layout_trunks - 1; i > n; i--)
	    editor->layout_trunk[i] = editor->layout_trunk[i - 1]; 


	EditorSetStatusMessage(editor, "Creating new Manual Page...");

	/* Increment untitled count and set untitled_title */
	core->untitled_count++;
	g_snprintf(
	    untitled_title, sizeof(untitled_title),
	    "Untitled%i.1",
	    core->untitled_count
	);

	/* Now branch_insert should be the branch to insert before or
	 * NULL
	 */
	text[0] = untitled_title;
	new_branch = gtk_ctree_insert_node(
	    ctree,
	    NULL,			/* No parent, it's toplevel */
	    branch_insert,		/* Insert before this 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,
	    FALSE,			/* Is leaf */
	    TRUE			/* Expanded */
	);

	/* Record new branch on layout_trunk pointer array index n */
	editor->layout_trunk[n] = new_branch;

	/* Create editor item structure */
	item = EditorItemNew(
	    EditorItemTypeFile,
	    editor,
	    ctree,
	    NULL,		/* Parent branch */
	    new_branch,		/* This branch node */
	    FALSE,		/* Is leaf */
	    NULL, 0		/* Lines list */
	);
	if(item != NULL)
	{
	    g_free(item->name);
	    item->name = STRDUP(text[0]);

	    /* No file name full, implies not saved yet */
	    g_free(item->full_path);
	    item->full_path = NULL;

	    item->has_changes = TRUE;
	}
	EditorBranchSetData(
	    ctree, new_branch,
	    item, EditorItemDestroyCB
	);

	/* Select the new branch */
	EditorBranchSelect(editor, new_branch);

	EditorUpdateMenus(editor);
	EditorSetStatusMessage(editor, "Created new Manual Page");
	EditorSetStatusProgress(editor, 0.0f);

	editor->freeze_count--;
}

/*
 *	Open manual page callback.
 */
void EditorManualOpenCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkWidget *toplevel;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	core = editor->core;

	/* Get selected branch (may be NULL) */
	branch = editor->selected_branch;

	if(TRUE)
	{
	    gboolean response;
	    gchar *msg, **path_rtn;
	    gint nftypes = 0, path_total_rtns;
	    fb_type_struct **ftypes_list = NULL, *type_rtn;

	    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",
		editor->last_open_path,
		ftypes_list, nftypes,
		&path_rtn, &path_total_rtns, &type_rtn
	    );
	    FileBrowserSetTransientFor(NULL);
	    FileBrowserDeleteTypeList(ftypes_list, nftypes);
	    if(response)
	    {
		gint i, status;
		const gchar *filename;

		for(i = 0; i < path_total_rtns; i++)
		{
		    filename = path_rtn[i];
		    if(filename == NULL)
			continue;

		    /* Open manual page file, the given branch will be
		     * recursed to its toplevel parent and the new
		     * manual page will be inserted as a toplevel trunk
		     * after the given branch toplevel trunk.
		     */
		    status = EditorFileOpen(
			editor, filename, branch, FALSE
		    );
		    switch(status)
		    {
		      case 0:
			/* Success, check if loaded file is writeable */
			if(access((const char *)filename, W_OK))
			{
			    /* Warn that file is read only */
			    CDialogSetTransientFor(toplevel);
			    CDialogGetResponse(
"Manual Page Read-Only",
"This manual page is set read-only,\n\
you will not be able to save it.",
				NULL,
				CDIALOG_ICON_WARNING,
				CDIALOG_BTNFLAG_OK,
				CDIALOG_BTNFLAG_OK
			    );
			    CDialogSetTransientFor(NULL);
			}
			break;

		      /* No such file */
		      case -2:
			msg = g_strdup_printf(
			    "No such file:\n\n    %s",
			    filename
			);
			CDialogSetTransientFor(toplevel);
			CDialogGetResponse(
			    "Manual Page Not Found",
			    msg,
			    NULL,
			    CDIALOG_ICON_WARNING,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);
			CDialogSetTransientFor(NULL);
			g_free(msg);
			break;

		      /* Other error */
		      default:
			CDialogSetTransientFor(toplevel);
			CDialogGetResponse(
"Open Manual Page Failed",
"Error occured while opening the manual page.",
			    NULL,
			    CDIALOG_ICON_ERROR,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);
			CDialogSetTransientFor(NULL);
			break;
		    }
		}
	    }
	    else
	    {
		/* User canceled save as, abort entire open procedure */
		editor->freeze_count--;
		return;
	    }
	}

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

	editor->freeze_count--;
}

/*
 *	New manual page from template.
 */
void EditorManualNewFromTemplateCB(GtkWidget *widget, gpointer data)
{
	const gchar *s;
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkWidget *toplevel;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	core = editor->core;

	/* Get selected branch (may be NULL) */
	branch = editor->selected_branch;

	if(TRUE)
	{
	    struct stat stat_buf;
	    gboolean response;
	    gint nftypes = 0, path_total_rtns;
	    gchar	**path_rtn,
			template_path[PATH_MAX + NAME_MAX];
	    fb_type_struct **ftypes_list = NULL, *type_rtn;

	    /* Get template path */
	    s = PrefParmGetValueP(
		core->pref,
		MEDIT_PREF_PARM_LOCATIONS_GLOBAL_DIR
	    );
	    if(s == NULL)
		s = MEDIT_GLOBAL_DIR;
	    if(s != NULL)
	    {
		s = PrefixPaths(s, MEDIT_TEMPLATES_DIR);
		if(s == NULL)
		    s = "/";
		strncpy(template_path, s, sizeof(template_path));
		template_path[sizeof(template_path) - 1] = '\0';
	    }
	    else
	    {
		strncpy(template_path, "/", sizeof(template_path));
		template_path[sizeof(template_path) - 1] = '\0';
	    }

	    /* Warn if the templates directory does not exist */
	    if(stat((const char *)template_path, &stat_buf))
	    {
		gchar *msg = g_strdup_printf(
"Unable to find the Manual Page templates directory:\n\n    %s",
		    template_path
		);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Unable To Find Directory",
		    msg,
"The specified Manual Page templates directory\n\
could not be found. This directory contains all\n\
Manual Page template files for this program.\n\
Verify that the specified paths for this program\n\
are defined properly in the preferences, go to\n\
edit->preferences->locations.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(msg);
		editor->freeze_count--;
		return;
	    }

	    /* Query the user for the manual page template */
            FileBrowserTypeListNew(
                &ftypes_list, &nftypes,
                ".mpt",
                "Manual page template"
	    );
	    FileBrowserTypeListNew(
		&ftypes_list, &nftypes,
		"*.*", "All files"
	    );

	    FileBrowserSetTransientFor(toplevel);
	    response = FileBrowserGetResponse(
		"Open Manual Page Template",
		"Open", "Cancel",
		template_path,
		ftypes_list, nftypes,
		&path_rtn, &path_total_rtns, &type_rtn
	    );
	    FileBrowserSetTransientFor(NULL);
	    FileBrowserDeleteTypeList(ftypes_list, nftypes);
	    if(response)
	    {
		gint i, status;
		gchar *msg;
		const gchar *filename;

		for(i = 0; i < path_total_rtns; i++)
		{
		    filename = path_rtn[i];
		    if(filename == NULL)
			continue;

		    /* Open manual page file as a template, the given
		     * branch will be recursed to its toplevel parent
		     * and the new manual page will be inserted as a
		     * toplevel trunk after the given branch toplevel
		     * trunk.
		     */
		    status = EditorFileOpen(
			editor,
			filename,
			branch,
			TRUE		/* Open as template */
		    );
		    switch(status)
		    {
		      case 0:
			/* Success */
			break;

		      /* No such file */
		      case -2:
			msg = g_strdup_printf(
			    "No such file:\n\n    %s",
			    filename
			);
			CDialogSetTransientFor(toplevel);
			CDialogGetResponse(
			    "Manual Page Not Found",
			    msg,
			    NULL,
			    CDIALOG_ICON_WARNING,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);
			CDialogSetTransientFor(NULL);
			g_free(msg);
			break;

		      /* Other error */
		      default:
			CDialogSetTransientFor(toplevel);
			CDialogGetResponse(
"Open Manual Page Failed",
"An error occured while opening the manual page.",
			    NULL,
			    CDIALOG_ICON_ERROR,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);
			CDialogSetTransientFor(NULL);
			break;
		    }
		}
	    }    
	    else
	    {
		/* User canceled save as, abort entire open procedure */
		editor->freeze_count--;
		return;
	    }
	}

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

	editor->freeze_count--;
}

/*
 *	Save manual page callback.
 */
void EditorManualSaveCB(GtkWidget *widget, gpointer data)
{
	gint status;
	const gchar *filename = NULL;
	GtkCTree *ctree;
	GtkCTreeNode *trunk, *branch;
	GtkCTreeRow *branch_row;
	GtkWidget *toplevel;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);

	/* Get selected branch and then its toplevel trunk */
	branch = editor->selected_branch;
	trunk = EditorItemGetToplevel(editor, branch);

	/* Must have trunk branch to save */
	if(trunk == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Non-Critical Internal Error!",
"Unable to find the toplevel trunk branch for the\n\
selected branch.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    editor->freeze_count--;
	    return;
	}
	else
	{
	    /* Get full file name, this can be NULL implying its
	     * never been saved before.
	     */
	    item = EditorBranchGetData(ctree, trunk);
	    if(item != NULL)
		filename = item->full_path;
	}

	/* If no filename was associated with the selected manual then
	 * prompt user for save as.
	 */
	if(filename == NULL)
	{
	    editor->freeze_count--;
	    EditorManualSaveAsCB(widget, data);
	    return;
	}

	/* Call save procedure, it will fetch the filename from the
	 * given toplevel trunk branch.
	 */
	status = EditorFileSave(editor, trunk);
	if(status)
	{
	    /* Error occured while saving */
	    gchar *msg;
	    switch(status)
	    {
	      /* User aborted on confirmation of overwrite */
	      case -3:
		break;

	      /* Cannot save, not writeable */
	      case -2:
		msg = g_strdup_printf(
"Unable to save the manual page to the file:\n\n    %s",
		    filename
		);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Save Manual Page Failed",
		    msg,
"The specified manual page file could not be written\n\
to, please verify that the specified path exists and has\n\
write permissions set.",
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(msg);
		break;

	      default:
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Manual Page Save Failed",
"An error occured while saving the manual page.",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		break;
	    }
	}
	else
	{
	    /* Mark child branches as no longer having changes */
	    branch = trunk;
	    branch_row = GTK_CTREE_ROW(branch);
	    if(branch_row == NULL)
		branch = NULL;
	    else
		branch = branch_row->children;

	    /* Iterate through each branch */
	    while(branch != NULL)
	    {
		item = EditorBranchGetData(ctree, branch);
		if(item != NULL)
		    item->has_changes = FALSE;

		/* Get sibling of branch */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row == NULL)
		    branch = NULL;
		else
		    branch = branch_row->sibling;
	    }

	    /* Mark trunk as no longer having changes */
	    item = EditorBranchGetData(ctree, trunk);
	    if(item != NULL)
		item->has_changes = FALSE;
	}


	/* Update menus, note that a successful save may have updated
	 * menus already but it is safe to do it again here.
	 */
	EditorUpdateMenus(editor);

	editor->freeze_count--;
}

/*
 *	Save manual page as callback.
 */
void EditorManualSaveAsCB(GtkWidget *widget, gpointer data)
{
	gint status;
	const gchar *filename = NULL;
	GtkCTree *ctree;
	GtkCTreeNode *trunk, *branch;
	GtkCTreeRow *branch_row;
	GtkWidget *toplevel;
	medit_core_struct *core;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	core = editor->core;

	/* Get selected branch and then its toplevel trunk */
	branch = editor->selected_branch;
	trunk = EditorItemGetToplevel(editor, branch);

	/* Must have trunk branch to save */
	if(trunk == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the toplevel trunk branch for the\n\
selected branch.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK, 
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    editor->freeze_count--;
	    return;
	}

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

	    /* Query the user to save the 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(
		"Save Manual Page",
		"Save", "Cancel",
		editor->last_save_as_path,
		ftypes_list, nftypes,
		&path_rtn, &path_total_rtns, &type_rtn
	    );
	    FileBrowserSetTransientFor(NULL);
	    FileBrowserDeleteTypeList(ftypes_list, nftypes);
	    if(response)
	    {
		filename = (path_total_rtns > 0) ? path_rtn[0] : NULL;
	    }
	}
	if(filename == NULL)
	{
	    editor->freeze_count--;
	    return;
	}

	/* Call save procedure, it will update the file name on
	 * the trunk's item data.
	 */
	status = EditorFileSaveAs(editor, filename, trunk);
	if(status)
	{
	    /* An error occured while saving */
	    gchar *msg;
	    switch(status)
	    {
	      /* User aborted on confirmation of overwrite */
	      case -3:
		break;

	      case -2:
		msg = g_strdup_printf(
"Unable to save the manual page to the file:\n\n    %s",
		    filename
		);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Save Manual Page Failed",
		    msg,
"The specified manual page file could not be written\n\
to, please verify that the given path exists and has\n\
write permissions set.",
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(msg);
		break;

	      default:
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Save Manual Page Failed",
"An error occured while saving the manual page.",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		break;
	    }
	}
	else
	{
	    /* Save successful, mark all child branches as no longer
	     * having changes
	     */
	    branch = trunk;
	    branch_row = GTK_CTREE_ROW(branch);
	    branch = (branch_row != NULL) ?
		branch_row->children : NULL;

	    /* Iterate through each branch */
	    while(branch != NULL)
	    {   
		item = EditorBranchGetData(ctree, branch);
		if(item != NULL)
		    item->has_changes = FALSE;

		/* Get sibling of branch */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row == NULL)
		    branch = NULL;
		else
		    branch = branch_row->sibling;
	    }

	    /* Mark trunk as no longer having changes */
	    item = EditorBranchGetData(ctree, trunk);
	    if(item != NULL)
		item->has_changes = FALSE;
	}

	FileBrowserReset();

	/* Update menus, note that a successful save may have updated
	 * menus already but it is safe to do it again here.
	 */     
	EditorUpdateMenus(editor);

	editor->freeze_count--;
}

/*
 *	Revert callback.
 */
void EditorManualRevertCB(GtkWidget *widget, gpointer data)
{
	gint status;
	gchar *buf;
	GtkCTree *ctree;
	GtkCTreeNode *branch, *branch_prev;
	GtkCTreeRow *branch_row;
	GtkWidget *toplevel;
	editor_item_struct *item;
	gchar *full_path;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);

	branch = editor->selected_branch;
	branch = EditorItemGetToplevel(editor, branch);
	if(branch == NULL)
	{
	    editor->freeze_count--;
	    return;
	}

	item = EditorBranchGetData(ctree, branch);
	if(item == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the item data for the selected\n\
Manual Page.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    editor->freeze_count--;
	    return;
	}

	/* Item's file name full is NULL? Implying it was never saved
	 * before?
	 */
	if(item->full_path == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No file data to revert to!",
"The selected Manual Page has no associated file,\n\
which implies that it has never been saved before.\n\
There is no file data to revert to.",
		NULL,
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    editor->freeze_count--;
	    return;
	}


	/* Confirm before revert */
	buf = g_strdup_printf(
"Are you sure you want to revert Manual Page\n`%s'?",
	    item->name
	);
	CDialogSetTransientFor(toplevel);
	status = CDialogGetResponse(
"Confirm Revert Manual Page",
	    buf,
	    NULL,
	    CDIALOG_ICON_WARNING,
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
	    CDIALOG_BTNFLAG_NO
	);
	CDialogSetTransientFor(NULL);
	g_free(buf);
	switch(status)
	{                    
	  case CDIALOG_RESPONSE_NOT_AVAILABLE:
	  case CDIALOG_RESPONSE_CANCEL:
	  case CDIALOG_RESPONSE_NO:
	    /* Do not revert Manual Page */
	    editor->freeze_count--;
	    return;
	    break;
 
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	  case CDIALOG_RESPONSE_YES:
	    break;
	}


	EditorSetStatusMessage(editor, "Reverting...");

	/* Make a copy of the item data's full file name */
	full_path = STRDUP(item->full_path);

	/* Get previous sibling to the currently selected branch,
	 * this will be used as the toplevel trunk branch to insert
	 * after.
	 */
	branch_prev = EditorItemGetFirstToplevel(editor);
	while(branch_prev != NULL)
	{
	    branch_row = GTK_CTREE_ROW(branch_prev);
	    if(branch_row == NULL)
	    {
		branch_prev = NULL;
	    }
	    else
	    {
		if(branch == branch_row->sibling)
		    break;
		else
		    branch_prev = branch_row->sibling;
	    }
	}

	EditorSetStatusMessage(editor, "Closing manual page...");

	/* Remove the trunk and all subsequent branch nodes, the  
	 * item data will be deallocated when the destroy notify  
	 * function is called and layout trunk list on editor will
	 * be updated.
	 */
	gtk_ctree_remove_node(ctree, branch);
	branch = NULL;


	/* Load from file again */
	status = EditorFileOpen(
	    editor, full_path, branch_prev, FALSE
	);
	switch(status)
	{
	  case 0:
	    break;

	  case -2:
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Revert Failed",
"The orignal file containing the manual page data\n\
could not be found. It may have been moved or deleted,\n\
try opening it manually by going to File->Open.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    break;

	  default:
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Revert Failed",
"An error occured while reloading the selected manual\n\
page. Try reloading manual by going to File->Open.",
		NULL,
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    break;
	}

	g_free(full_path);

	EditorSetStatusMessage(editor, "Revert done");

	editor->freeze_count--;
}

/*
 *	Close manual page callback (does not close the Manual Page Editor
 *	window, for that see EditorCloseMCB()).
 */
void EditorManualCloseCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkCTreeRow *branch_row;
	GtkWidget *toplevel;
	editor_item_struct *item;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	core = editor->core;

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

	/* Apply data on currently selected branch to ensure that we
	 * have the most recent data.
	 */
	EditorDoApplyValues(editor, branch);

	/* Recurse to selected branch toplevel trunk parent */
	while(TRUE)
	{
	    branch_row = GTK_CTREE_ROW(branch);
	    if(branch_row == NULL)
	    {
		editor->freeze_count--;
		return;
	    }
	    if(branch_row->parent == NULL)
		break;
	    else
		branch = branch_row->parent;
	}
	if(branch == NULL)
	{
	    editor->freeze_count--;
	    return;
	}
	/* Branch should now be the toplevel trunk */


	/* Get item data */
	item = EditorBranchGetData(ctree, branch);
	if(item == NULL)
	{
	    /* Could not get item data, but remove anyways */
	    gtk_ctree_remove_node(ctree, branch);
	    editor->freeze_count--;
	    return;
	}


	/* Check if it has changes by checking the toplevel trunk
	 * branch node's item data, it reflects if any child has changes.
	 */
	if(item->has_changes)
	{
	    /* Has changes, prompt for save */
	    gint status;
	    gchar *buf = g_strdup_printf(
		"Save changes to manual page `%s'?",
		item->name  
	    );
	    CDialogSetTransientFor(toplevel);
	    status = CDialogGetResponse(
		"Save Changes",
		buf,
		NULL,
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
		    CDIALOG_BTNFLAG_CANCEL,
		CDIALOG_BTNFLAG_YES
	    );
	    CDialogSetTransientFor(NULL);
	    g_free(buf);
	    switch(status)
	    {
	      case CDIALOG_RESPONSE_YES:
	      case CDIALOG_RESPONSE_YES_TO_ALL:
	      case CDIALOG_RESPONSE_OK:
		/* Request to save, check if the full path is set.
		 * If the full path is not set (regardless of if the
		 * name is set or not, we need to consider that
		 * this file was never saved before and thus we need
		 * to request from the user a `save as'.
		 */
		if(item->full_path == NULL)
		{
		    /* Save as */
		    gboolean response;
		    gchar **path_rtn;
		    gint nftypes = 0, path_total_rtns;
		    fb_type_struct **ftypes_list = NULL, *type_rtn;

		    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( 
			"Save Manual Page As",
			"Save", "Cancel",
			editor->last_save_as_path,
			ftypes_list, nftypes,
			&path_rtn, &path_total_rtns, &type_rtn
		    );
		    FileBrowserSetTransientFor(NULL);
                    FileBrowserDeleteTypeList(ftypes_list, nftypes);
		    if(response && (path_total_rtns > 0))
		    {
			const gchar *path = path_rtn[0];
			EditorFileSaveAs(editor, path, branch);
			FileBrowserReset();
		    }
		    else
		    {
			/* User canceled save as, abort entire close
			 * procedure
			 */
			editor->freeze_count--;
			return;
		    }
		}
		else
		{           
		    /* Save */
		    EditorFileSave(editor, branch);
		}
		break;

	      case CDIALOG_RESPONSE_NO:
		break;

	      case CDIALOG_RESPONSE_CANCEL:
	      case CDIALOG_RESPONSE_NOT_AVAILABLE:
		/* Response not available, abort entire close
		 * procedure
		 */  
		editor->freeze_count--;
		return;
		break;
	    }
	}


	/* Remove the trunk and all subsequent branch nodes, the
	 * item data will be deallocated when the destroy notify
	 * function is called and layout trunk list on editor will
	 * be updated.
	 */
	gtk_ctree_remove_node(ctree, branch);
	branch = NULL;

	EditorSetStatusMessage(editor, "Manual page closed");

	/* Check if a branch is still selected, if it is then set it
	 * to NULL. This should not happen since the selected branch node
	 * should have been sent an unselect signal just before it was
	 * deleted.
	 */
	if(editor->selected_branch != NULL)
	{
	    editor->selected_branch = NULL;

	    /* Clear undo/redo list just in case, since branch node
	     * unselect callback was probably not called.
	     */
	    EditorResetUndos(editor);
	}

	/* If no more layout trunks loaded then mark this editor
	 * as not having any changes
	 */

	EditorUpdateMenus(editor);

	editor->freeze_count--;
}

/*
 *	Add header to currently selected manual page.
 *
 *	Checks first if selected manual page already has a header
 *	and warns user.
 */
void EditorManualAddHeaderCB(GtkWidget *widget, gpointer data)
{
	gchar *text[1];
	GdkPixmap *closed_pm, *opened_pm;
	GdkBitmap *closed_mask, *opened_mask;
	GtkCTree *ctree;
	GtkCTreeRow *branch_row;
	GtkCTreeNode *branch, *trunk, *new_branch = NULL;
	GtkWidget *toplevel;
	medit_core_struct *core;
	medit_pixmaps_list_struct *pixmaps_list;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

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

	pixmaps_list = &core->pixmaps_list;

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	if(ctree == NULL)
	    return;

	/* Get selected branch */
	branch = editor->selected_branch;
	if(branch == NULL)
	{
	    /* Warn about no branch being selected */
	    if(editor->total_layout_trunks > 0)
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Add Header Failed",
"You need to select the insert position of\n\
where you want to add the new header. This\n\
can be a manual page reference or an existing\n\
header or section to add under.",
"You have requested to add a new header but\n\
you have not specified a manual page or\n\
header or section to add it under. You must\n\
selected a manual page or an existing header\n\
or section to add the new header under.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    else
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Add Header Failed",
"There are no manual pages opened to add\n\
a new header to.",
"You must either create a new manual page or\n\
open an existing manual page to add a new header\n\
to.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    return;
	}

	/* Get toplevel trunk for the selected branch */
	trunk = EditorItemGetToplevel(editor, branch);
	if(trunk == NULL)
	{
	    /* Could not get toplevel trunk, something is very wrong */
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Add Header Failed",
"Could not find the parent trunk for the selected\n\
layout branch.",
"An internal error has occured in which the parent\n\
trunk for the currently selected branch could not\n\
be found. There may be some inconsistancy in the\n\
loaded data. You should save this manual page to\n\
a new file (use `save as') and close this application\n\
to prevent and further data loss.\n",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}
	else
	{
	    /* Check if this manual page already has a header and warn
	     * user about it if it exists.
	     */

/* Need to work on this */


	}



	EditorSetBusy(editor);
	EditorSetStatusProgress(editor, 0.0);


	/* Set up data pointers */
	text[0] = "Header";
	closed_pm = pixmaps_list->manpage_heading_20x20;
	closed_mask = pixmaps_list->manpage_heading_20x20_mask;
	opened_pm = closed_pm;
	opened_mask = closed_mask;


	/* Get item data pointer for selected branch if possible */
	item = EditorBranchGetData(ctree, branch);
	if(item == NULL)
	{
	    /* Item data is NULL, so insert as first child on trunk.
	     * Get row pointer of the trunk.
	     */
	    branch_row = GTK_CTREE_ROW(trunk);
	    if(branch_row != NULL)
	    {
		/* Trunk must not be a leaf */
		if(!branch_row->is_leaf)
		{
		    /* Insert before first child in trunk */
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->children,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE,
			FALSE
		    );
		}
	    }
	}
	else
	{
	    /* Insert by branch item data type */
	    switch(item->type)
	    {
	      case EditorItemTypeFile:
		/* Get row pointer from selected branch which is
		 * a trunk
		 */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row != NULL)
		{
		    /* If selected branch is of type EditorItemTypeFile
		     * then it must not be a leaf
		     */
		    if(branch_row->is_leaf)
			break;

		    /* Insert before first child in selected branch
		     * (which is suppose to be a trunk
		     */
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->children,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE,
			FALSE
		    );
		}
		break;

	      case EditorItemTypeHeader:
	      case EditorItemTypeSection:
		/* Get row pointer from selected branch */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row != NULL)
		{
		    /* If selected branch must be a leaf */
		    if(!branch_row->is_leaf)
			break;

		    /* Insert after selected branch by passing the
		     * selected branch sibling as the insert position
		     * (which may be NULL)
		     */
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->sibling,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE,
			FALSE
		    );
		}
		break;
	    }
	}

	/* If inserting of new branch node was successful, then new_branch
	 * should not be NULL.
	 */
	if(new_branch != NULL)
	{
	    /* Create new item data for the new branch node */
	    item = EditorItemNew(
		EditorItemTypeHeader,
		editor,
		ctree,
		trunk,		/* Parent branch */
		new_branch,	/* This branch node */
		TRUE,		/* Is leaf */
		NULL, 0		/* Lines list */
	    );
	    EditorBranchSetData(
		ctree, new_branch,
		item, EditorItemDestroyCB
	    );

	    /* Mark has changes */
	    EditorItemSetToplevelHasChanges(editor, new_branch, TRUE);
	}


	/* Update layout clist column width */
	gtk_clist_set_column_width(
	    (GtkCList *)ctree,
	    0,                  /* Column */
	    gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
	);

	/* Select new branch node */
	EditorBranchSelect(editor, new_branch);

	EditorSetStatusProgress(editor, 0.0);
	EditorSetStatusMessage(editor, "Added new header");

	/* Update menus on editor */
	EditorUpdateMenus(editor);

	EditorSetReady(editor);
}

/*
 *	Adds a new section to the manual page.
 */
void EditorManualAddSectionCB(GtkWidget *widget, gpointer data)
{
	gchar *text[1];
	GdkPixmap *closed_pm, *opened_pm;
	GdkBitmap *closed_mask, *opened_mask;
	GtkCTree *ctree;
	GtkCTreeRow *branch_row;
	GtkCTreeNode *branch, *trunk, *new_branch = NULL;
	GtkWidget *toplevel;
	medit_core_struct *core;
	medit_pixmaps_list_struct *pixmaps_list;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

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

	pixmaps_list = &core->pixmaps_list;

	toplevel = EDITOR_TOPLEVEL(editor->toplevel);
	ctree = GTK_CTREE(editor->layout_ctree);
	if(ctree == NULL)
	    return;

	/* Get selected branch */
	branch = editor->selected_branch;
	if(branch == NULL)
	{
	    /* Warn about no branch being selected */
	    if(editor->total_layout_trunks > 0)
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Add Section Failed",
"You need to select the insert position of\n\
where you want to add the new section. This\n\
can be a manual page reference or an existing\n\
header or section to add under.",
"You have requested to add a new section but\n\
you have not specified a manual page or\n\
header or section to add it under. You must\n\
selected a manual page or an existing header\n\
or section to add the new section under.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    else
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"Add Section Failed",
"There are no manual pages opened to add\n\
a new section to.",
"You must either create a new manual page or\n\
open an existing manual page to add a new section\n\
to.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    return;
	}

	/* Get toplevel trunk for the selected branch */
	trunk = EditorItemGetToplevel(editor, branch);
	if(trunk == NULL)
	{
	    /* Could not get toplevel trunk, something is very wrong */
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Add Section Failed",
"Unable to find the parent trunk for the selected\n\
layout branch.",
"An internal error has occured in which the parent\n\
trunk for the currently selected branch could not\n\
be found. There may be some inconsistancy in the\n\
loaded data. You should save this manual page to\n\
a new file (use `save as') and close this application\n\
to prevent and further data loss.\n",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}


	EditorSetBusy(editor);
	EditorSetStatusProgress(editor, 0.0);


	/* Set up data pointers */
	text[0] = "UNTITLED";
	closed_pm = pixmaps_list->manpage_section_20x20;
	closed_mask = pixmaps_list->manpage_section_20x20_mask;
	opened_pm = closed_pm;
	opened_mask = closed_mask;

	/* Get item data pointer for selected branch if possible */
	item = EditorBranchGetData(ctree, branch);
	if(item == NULL) 
	{
	    /* Item data is NULL, so insert as first child on trunk.
	     * Get row pointer of the trunk.
	     */
	    branch_row = GTK_CTREE_ROW(trunk);
	    if(branch_row != NULL)
	    {
		/* Trunk must not be a leaf */
		if(!branch_row->is_leaf)
		{
		    /* Insert before first child in trunk */   
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->children,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE,
			FALSE
		    ); 
		}
	    }
	}    
	else
	{
	    /* Insert by branch item data type */
	    switch(item->type)
	    {
	      case EditorItemTypeFile:
		/* Get row pointer from selected branch which is
		 * a trunk
		 */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row != NULL)
		{
		    /* If selected branch is of type EditorItemTypeFile
		     * then it must not be a leaf
		     */
		    if(branch_row->is_leaf)
			break;

		    /* Insert before first child in selected branch
		     * (which is suppose to be a trunk
		     */
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->children,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE, 
			FALSE
		    );
		}
		break;

	      case EditorItemTypeHeader:
	      case EditorItemTypeSection:
		/* Get row pointer from selected branch */
		branch_row = GTK_CTREE_ROW(branch);
		if(branch_row != NULL)
		{
		    /* If selected branch must be a leaf */
		    if(!branch_row->is_leaf)
			break;
	
		    /* Insert after selected branch by passing the
		     * selected branch sibling as the insert position
		     * (which may be NULL).
		     */
		    new_branch = gtk_ctree_insert_node(
			ctree, trunk, branch_row->sibling,
			text, MEDIT_LIST_ICON_TEXT_SPACING,
			closed_pm, closed_mask,
			opened_pm, opened_mask,
			TRUE,
			FALSE
		    );
		}
		break;
	    } 
	}

	/* If inserting of new branch node was successful, then new_branch
	 * should not be NULL.
	 */
	if(new_branch != NULL)
	{
	    /* Create new item data for the new branch node */
	    item = EditorItemNew(
		EditorItemTypeSection,
		editor,
		ctree,
		trunk,		/* Parent branch */
		new_branch,	/* This branch node */
		TRUE,		/* Is leaf */
		NULL, 0		/* Lines list */
	    );
	    if(item != NULL)
	    {
		g_free(item->section_name);
		item->section_name = STRDUP(text[0]);
	    }
	    EditorBranchSetData(
		ctree, new_branch,
		item, EditorItemDestroyCB
	    );

	    /* Mark has changes */
	    EditorItemSetToplevelHasChanges(editor, new_branch, TRUE);
	}


	/* Update layout clist column width */
	gtk_clist_set_column_width(
	    (GtkCList *)ctree,
	    0,                  /* Column */
	    gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
	);

	/* Select new branch node */
	EditorBranchSelect(editor, new_branch);

	EditorSetStatusProgress(editor, 0.0);
	EditorSetStatusMessage(editor, "Added new section");

	/* Update menus on editor */
	EditorUpdateMenus(editor);

	EditorSetReady(editor);
}

/*
 *	Removes a header or section from the currently selected
 *	manual page.
 */
void EditorManualRemoveSectionCB(GtkWidget *widget, gpointer data)
{
	gboolean yes_to_all = FALSE;
	gboolean need_break, need_continue;
	GtkCTree *ctree;
	GtkCTreeNode *branch;
	GtkWidget *toplevel;
	medit_core_struct *core;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

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

	toplevel = editor->toplevel;
	ctree = GTK_CTREE(editor->layout_ctree);
	if(ctree == NULL)
	    return;


	EditorSetBusy(editor);
	EditorSetStatusProgress(editor, 0.0);


	/* Get selected branch */
	branch = editor->selected_branch;
	while(branch != NULL)
	{
	    item = EditorBranchGetData(ctree, branch);
	    if(item == NULL)
	    {
		branch = NULL;
		continue;
	    }

	    /* Handle by type */
	    need_continue = FALSE;
	    need_break = FALSE;
	    switch(item->type)
	    {
	      case EditorItemTypeHeader:
		EditorSetStatusMessage(
		    editor,
		    "Removing header..."
		);
		if(!yes_to_all)
		{
		    gint status;

		    CDialogSetTransientFor(toplevel);
		    status = CDialogGetResponse(
"Confirm Remove Header",
"Are you sure you want to remove the header\n\
from the manual page?",
			NULL,
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
			CDIALOG_BTNFLAG_NO
		    );
		    CDialogSetTransientFor(NULL);
		    switch(status)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
			break;

		      case CDIALOG_RESPONSE_CANCEL:
		      case CDIALOG_RESPONSE_NOT_AVAILABLE:
			need_break = TRUE;
			break;

		      default:
			need_continue = TRUE;
			break;
		    }
		}
		break;

	      case EditorItemTypeSection:
		EditorSetStatusMessage(
		    editor,
		    "Removing section..."
		);
		if(!yes_to_all)
		{
		    gint status;
		    gchar *buf = g_strdup_printf(
"Are you sure you want to remove section\n`%s' from the manual page?",
			item->section_name
		    );
		    CDialogSetTransientFor(toplevel);
		    status = CDialogGetResponse(
"Confirm Remove Section",
			buf,
			NULL,
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
			CDIALOG_BTNFLAG_NO
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(buf);
		    switch(status)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
			break;

		      case CDIALOG_RESPONSE_CANCEL:
		      case CDIALOG_RESPONSE_NOT_AVAILABLE:
			need_break = TRUE;
			break;

		      default:
			need_continue = TRUE;
			break;
		    }
		}
		break;

	      default:
		/* Some other type, skip it */
		need_continue = TRUE;
		break;
	    }
	    if(need_break)
		break;
	    if(need_continue)
	    {
		branch = NULL;
		continue;
	    }

	    /* Remove this branch, the destroy notify callback will
	     * deallocate the branch item data and mark this branch
	     * as unselected.
	     */
	    gtk_ctree_remove_node(ctree, branch);

	    EditorSetStatusProgress(editor, 1.0);

	    branch = NULL;
	}


	/* Update layout clist column width */
	gtk_clist_set_column_width(
	    (GtkCList *)ctree,
	    0,			/* Column */
	    gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
	);

	EditorSetStatusProgress(editor, 0.0);
	EditorSetStatusMessage(editor, "Remove done");

	/* Update menus on editor */
	EditorUpdateMenus(editor);

	EditorSetReady(editor);
}

/*
 *	Manual page properties callback.
 */
void EditorManualPropertiesCB(GtkWidget *widget, gpointer data)
{
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;



}

/*
 *      Manual page preview callback.
 */
void EditorManualPreviewCB(GtkWidget *widget, gpointer data)
{
	gint i, viewer_num = -1, editor_num = -1;
	viewer_struct *viewer = NULL;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

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

	/* Get number of this editor on core structure */
	for(i = 0; i < core->total_editors; i++)
	{
	    if(editor == core->editor[i])
	    {
		editor_num = i;
		break;
	    }
	}

	/* Create a new viewer as needed */
	i = editor->viewer_num;
	if((i >= 0) && (i < core->total_viewers))
	{
	    viewer = core->viewer[i];
	    if(viewer != NULL)
		viewer_num = i;
	}

	/* If viewer is NULL then we need to create a new one */
	if(viewer == NULL)
	{
	    /* Create new viewer and add it to the core structure */
	    if(core->total_viewers < 0)
		core->total_viewers = 0;
	    i = core->total_viewers;
	    core->total_viewers = i + 1;
	    core->viewer = (viewer_struct **)g_realloc(
		core->viewer,
		core->total_viewers * sizeof(viewer_struct *)
	    );
	    if(core->viewer == NULL)
	    {
		core->total_viewers = 0;
		return;
	    }

	    viewer = ViewerNew(
		core, editor_num
	    );
	    core->viewer[i] = viewer;

	    if(viewer == NULL)
		viewer_num = -1;
	    else
		viewer_num = i;

	    /* Update viewer number on editor */
	    editor->viewer_num = viewer_num;
	}

	/* Viewer valid? */
	if(viewer != NULL)
	{
	    /* Map as needed */
	    if(!GTK_WIDGET_MAPPED(viewer->toplevel))
		ViewerMap(viewer);

	    EditorDoPreview(
		editor, editor->selected_branch,
		viewer_num
	    );
	}
}

/*
 *	Syntax highlight toggle callback.
 */
void EditorManualSyntaxHighlightToggleCB(GtkWidget *widget, gpointer data)
{
	gboolean state = FALSE;
	GtkWidget *toplevel;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	if(editor->freeze_count > 0)
	    return;

	editor->freeze_count++;

	toplevel = editor->toplevel;

	/* Syntax highlighting check menu item? */
	if(widget == editor->syntax_highlight_cmi)
	    state = GUIMenuItemGetCheck(widget);

	/* Toggle syntax highlighting on editor */
	editor->syntax_highlighting = state;

	/* If turning on syntax highlighting, then warn user about
	 * problems with GtkText widget
	 */
	if(state)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"Subsystems Warning",
"Enabling syntax highlighting may cause crashes!\n\
\n\
Click on `help' for explaination!",
"This program makes use of the GTK+ GtkText widget\n\
it has known bugs which cause applications to crash\n\
under certain circumstances such as syntax highlighting.\n\
You are advised not to use syntax highlighting, however\n\
if you do and a crash does occure then this program will\n\
try its best (no gaurantees) to emergency save any opened\n\
documents into the program's tempory directory before\n\
exiting.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	}

	/* Update menus */
	EditorUpdateMenus(editor);

	/* Get pointer to selected branch item data */
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree),
	    editor->selected_branch
	);
	if(item != NULL)
	{
	    /* Check if selected item is of type section */
	    if(item->type == EditorItemTypeSection)
	    {
		/* Need to apply and fetch values to ensure update
		 * on section_text widget's text. The function
		 * EditorDoFetchValues() will call syntax
		 * highlighting on the newly loaded text.
		 */
		EditorDoApplyValues(editor, editor->selected_branch);
		EditorDoFetchValues(editor, editor->selected_branch);
	    }
	}

	editor->freeze_count--;
}


/*
 *	Undo callback.
 */
void EditorUndoCB(GtkWidget *widget, gpointer data)
{
	gint status;
	GtkWidget *toplevel;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	/* Nothing to undo? */
	if(editor->total_undos <= 0)
	    return;

	toplevel = editor->toplevel;

	/* Mark that we're redoing */
	editor_processing_undo = editor;                                      
	EditorSetBusy(editor);

	/* Perform undo procedure, passing the input list as the undo
	 * list and the redo list as the output list. The first item
	 * on the in list will be applied and an inverse operation
	 * will be placed into the output list.
	 */
	status = EditorUndoDoApply(
	    editor,
	    &editor->undo, &editor->total_undos,
	    &editor->redo, &editor->total_redos,
	    editor->max_undos
	);

	/* Flush and signals generated during undo apply */
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();

	/* Mark as no longer undoing */
	editor_processing_undo = NULL;
	EditorSetReady(editor);

	/* Undo failed? */
	if(status)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
		"Undo Failed",
"The undo process could not be performed.",
"Circumstances or an error prevented the undo\n\
operation from being performed.",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	}

	EditorUpdateMenus(editor);
}

/*
 *	Redo callback.
 */
void EditorRedoCB(GtkWidget *widget, gpointer data)
{
	gint status;
	GtkWidget *toplevel;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	/* Nothing to redo? */
	if(editor->total_redos <= 0)
	    return;

	toplevel = editor->toplevel;

	/* Mark that we're redoing */
	editor_processing_undo = editor;
	EditorSetBusy(editor);

	/* Perform redo procedure, passing the input list as the redo
	 * list and the undo list as the output list. The first item
	 * on the in list will be applied and an inverse operation
	 * will be placed into the output list.
	 */
	status = EditorUndoDoApply(
	    editor,
	    &editor->redo, &editor->total_redos,
	    &editor->undo, &editor->total_undos,
	    editor->max_undos
	);

	/* Flush and signals generated during undo apply */
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();

	/* Mark as no longer undoing */
	editor_processing_undo = NULL;
	EditorSetReady(editor);

	/* Undo failed? */
	if(status)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
		"Redo Failed",
"The redo process could not be performed.",
"Circumstances or an error prevented the redo\n\
operation from being performed.",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	}

	EditorUpdateMenus(editor);
}


/*
 *	Cut.
 */
void EditorCutCB(GtkWidget *widget, gpointer data)
{
	gint start_pos, end_pos;
	GtkCTreeNode *sel_node;
	GtkEditable *editable;
	GtkText *text;
	editor_item_struct *item;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	core = editor->core;

	/* Get the selected item */
	sel_node = editor->selected_branch;
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), sel_node
	);
	if(item == NULL)
	    return;

	/* Get the GtkText based on the selected item type */
	text = NULL;
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    text = GTK_TEXT(editor->header_text);
	    break;
	  case EditorItemTypeSection:
	    text = GTK_TEXT(editor->section_text);
	    break;
	  case EditorItemTypeFile:
	    break;
	}           
	if(text == NULL)
	    return;

	editable = GTK_EDITABLE(text);
	start_pos = editable->selection_start_pos,
	end_pos = editable->selection_end_pos;
	if(end_pos < start_pos)
	{
	    const gint i = end_pos;
	    end_pos = start_pos;
	    start_pos = i;
	}

	/* Has selection and valid selection bounds? */
	if(editable->has_selection &&
	   (start_pos > -1) && (end_pos > -1) &&
	   ((end_pos - start_pos) > 0)
	)
	{
	    gtk_text_freeze(text);
	    gtk_editable_cut_clipboard(editable);
	    gtk_text_thaw(text);
	    EditorItemSetToplevelHasChanges(
		editor, sel_node, TRUE
	    );
	}

	EditorUpdateMenus(editor);
	EditorSetStatusMessage(editor, "Selection cut");
}

/*
 *	Copy.
 */
void EditorCopyCB(GtkWidget *widget, gpointer data)
{
	gint start_pos, end_pos;
	GtkCTreeNode *sel_node;
	GtkEditable *editable;
	GtkText *text;
	editor_item_struct *item;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	core = editor->core;

	/* Get the selected item */
	sel_node = editor->selected_branch;
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), sel_node
	);
	if(item == NULL)
	    return;

	/* Get the GtkText based on the selected item type */
	text = NULL;
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    text = GTK_TEXT(editor->header_text);
	    break;
	  case EditorItemTypeSection:
	    text = GTK_TEXT(editor->section_text);
	    break;
	  case EditorItemTypeFile:
	    break;
	}           
	if(text == NULL)
	    return;

	editable = GTK_EDITABLE(text);
	start_pos = editable->selection_start_pos,
	end_pos = editable->selection_end_pos;
	if(end_pos < start_pos)
	{
	    const gint i = end_pos;
	    end_pos = start_pos;
	    start_pos = i;
	}

	/* Has selection and valid selection bounds? */
	if(editable->has_selection &&
	   (start_pos > -1) && (end_pos > -1) &&
	   ((end_pos - start_pos) > 0)
	)
	{
	    gtk_text_freeze(text);
	    gtk_editable_copy_clipboard(editable);
	    gtk_text_thaw(text);
	}

	EditorUpdateMenus(editor);
	EditorSetStatusMessage(editor, "Selection coppied");
}

/*
 *	Paste.
 */
void EditorPasteCB(GtkWidget *widget, gpointer data)
{
	GtkCTreeNode *sel_node;
	GtkEditable *editable;
	GtkText *text;
	editor_item_struct *item;
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	core = editor->core;
	sel_node = editor->selected_branch;
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), sel_node
	);
	if(item == NULL)
	    return;

	/* Get the GtkText based on the selected item type */
	text = NULL;
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    text = GTK_TEXT(editor->header_text);
	    break;
	  case EditorItemTypeSection:
	    text = GTK_TEXT(editor->section_text);
	    break;
	  case EditorItemTypeFile:
	    break;
	}           
	if(text == NULL)
	    return;

	editable = GTK_EDITABLE(text);

	gtk_text_freeze(text);
	gtk_editable_paste_clipboard(editable);
	gtk_text_thaw(text);

	EditorItemSetToplevelHasChanges(
	    editor, sel_node, TRUE
	);

	EditorUpdateMenus(editor);
	EditorSetStatusMessage(editor, "Paste done");
}

/*
 *	Delete Text.
 */
void EditorDeleteCB(GtkWidget *widget, gpointer data)
{

}

/*
 *	Select All.
 */
void EditorSelectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable = NULL;
	GtkText *text;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), editor->selected_branch
	);
	if(item == NULL)
	    return;

	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    editable = GTK_EDITABLE(editor->header_text);
	    break;
	  case EditorItemTypeSection:
	    editable = GTK_EDITABLE(editor->section_text);
	    break;
	  case EditorItemTypeFile:
	    break;
	}
	text = GTK_TEXT(editable);
	if(editable == NULL)
	    return;

	gtk_text_freeze(text);
	gtk_editable_select_region(editable, 0, -1);
	gtk_text_thaw(text);
}

/*
 *	Unselect All.
 */
void EditorUnselectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable = NULL;
	GtkText *text;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;
	
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), editor->selected_branch
	);
	if(item == NULL)
	    return;
	
	switch(item->type)
	{
	  case EditorItemTypeHeader: 
	    editable = GTK_EDITABLE(editor->header_text);
	    break;
 	  case EditorItemTypeSection:
	    editable = GTK_EDITABLE(editor->section_text);
	    break;
	  case EditorItemTypeFile:
	    break;
	}
	text = GTK_TEXT(editable);
	if(editable == NULL)
	    return;

	gtk_text_freeze(text);
	gtk_editable_select_region(editable, -1, -1);
	gtk_text_thaw(text);
}

/*
 *	Preferences.
 */
void EditorPreferencesCB(GtkWidget *widget, gpointer data)
{
	medit_core_struct *core;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	core = editor->core;
	if(core != NULL)
	{
	    pref_struct *pref = core->pref;
	    GtkCTreeNode *branch;
	    GtkCTreeRow *branch_row;
	    GtkCTree *ctree;
	    char *strptr;

	    ctree = (GtkCTree *)((pref == NULL) ?
		NULL : pref->catagory_ctree
	    );
	    if(ctree != NULL)
	    {
		branch = gtk_ctree_node_nth(ctree, 0);

		while(branch != NULL)
		{
		    strptr = PrefPanelGetBranchText(pref, branch);
		    if(strptr != NULL)
		    {
			if(!g_strcasecmp(strptr, "Appearance"))
			{
			    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);
	    }
	}

	return;
}


/* Macro to set the GtkText *text variable to the appropriate
 * value. Uses variables editor, branch, item, and text.
 */
#define EDITOR_GET_TEXT_WIDGET	{		\
 text = NULL;					\
 switch(item->type) {				\
  case EditorItemTypeHeader:			\
   break;					\
  case EditorItemTypeSection:			\
   text = GTK_TEXT(editor->section_text);	\
   break;					\
  case EditorItemTypeFile:			\
   break;					\
 }						\
}

/* Pre code macro for editor fmt callbacks, sets up variables;
 * font, style, text, editable, ctree, branch, item, insert_pos,
 * end_pos, core, styles_list, and editor.
 */
#define EDITOR_FMT_CB_PRE	\
	GdkFont *font; \
	GtkStyle *style; \
	GtkWidget *toplevel; \
	GtkText *text; \
	GtkEditable *editable; \
	GtkCTree *ctree; \
	GtkCTreeNode *branch; \
	editor_item_struct *item; \
	int insert_pos, end_pos; \
	medit_core_struct *core; \
	medit_styles_list_struct *styles_list; \
	editor_struct *editor = EDITOR(data); \
	if(editor == NULL) \
	    return; \
 \
	if(editor->freeze_count > 0) \
	    return; \
 \
	core = editor->core; \
	toplevel = editor->toplevel; \
	ctree = GTK_CTREE(editor->layout_ctree); \
	branch = editor->selected_branch; \
	if((core == NULL) || (ctree == NULL) || (branch == NULL)) \
	    return; \
 \
	styles_list = &core->styles_list; \
	style = styles_list->edit_text_standard; \
	font = ((style == NULL) ? NULL : style->font); \
 \
	item = EditorBranchGetData(ctree, branch); \
	if(item == NULL) \
	    return; \
 \
	EDITOR_GET_TEXT_WIDGET \
	if(text == NULL) \
	    return; \
	else \
	    editable = GTK_EDITABLE(text); \
 \
	if(editable->has_selection) \
	{ \
	    insert_pos = (gint)editable->selection_start_pos; \
	    end_pos = (gint)editable->selection_end_pos; \
	    if(insert_pos > end_pos) \
	    { \
		int tmp_pos = end_pos; \
		end_pos = insert_pos; \
		insert_pos = tmp_pos; \
	    } \
	} \
	else \
	{ \
	    insert_pos = (gint)editable->current_pos; \
	    end_pos = -1; \
	} \
 \

/* Post code macro for editor fmt callbacks, uses variables;
 * insert_pos, end_pos, new_str, new_str_len, editable, and editor.
 */
#define EDITOR_FMT_CB_POST	\
{ \
 gint position; \
 \
 position = insert_pos; \
 /* Need to call insert callback, notifying of changes, this will \
  * record an undo and mark has changes on the selected branch. \
  */ \
 EditorTextInsertCB( \
  editable, new_str, new_str_len, \
  &position, editor \
 ); \
 gtk_editable_set_position(editable, insert_pos + new_str_len); \
 gtk_text_set_point(GTK_TEXT(editable), insert_pos + new_str_len); \
 gtk_widget_grab_focus(GTK_WIDGET(editable)); \
}

/*
 *	Paragraph left callback.
 */
void EditorFmtParagraphLeftCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "<LP>";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Paragraph right callback.
 */
void EditorFmtParagraphRightCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "<RP>";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Paragraph indented callback.
 */
void EditorFmtParagraphIndentedCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "<IP>";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *      List item paragraph callback.
 */
void EditorFmtListItemCB(GtkWidget *widget, gpointer data)
{
	const gchar *new_str = "<TP>";
	gint new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Bold callback.
 */
void EditorFmtBoldCB(GtkWidget *widget, gpointer data)
{
	const gchar *new_str = "<fB>";
	gint new_str_len;
	EDITOR_FMT_CB_PRE
 
	new_str_len = strlen(new_str);
  
	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST

	/* Ending tag */
	new_str = "<fR>";
	if(end_pos < 0)
	{
	    insert_pos = insert_pos + new_str_len;
	    new_str_len = strlen(new_str);

	    gtk_editable_set_position(editable, insert_pos);
	    gtk_text_freeze(text);
	    gtk_text_insert(
		text, font, NULL, NULL,
		new_str, new_str_len
	    );
	    gtk_text_thaw(text);
	}
	else
	{
	    int new_end_pos = end_pos + new_str_len;

	    insert_pos = end_pos + new_str_len;
	    end_pos = -1;
	    new_str_len = strlen(new_str);
	    new_end_pos += new_str_len;
 
	    gtk_text_set_point(text, insert_pos);
	    gtk_text_freeze(text);
	    gtk_text_insert(
		text, font, NULL, NULL,
		new_str, new_str_len
	    );
	    gtk_text_thaw(text);
	}

	EDITOR_FMT_CB_POST
}

/*
 *      Underline callback.
 */
void EditorFmtUnderlineCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "<fI>";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);
 
	EDITOR_FMT_CB_POST

	/* Ending tag */
	new_str = "<fR>";
	if(end_pos < 0)
	{
	    insert_pos = insert_pos + new_str_len;
	    new_str_len = strlen(new_str);

	    gtk_editable_set_position(editable, insert_pos);              
	    gtk_text_freeze(text);
	    gtk_text_insert(
		text, font, NULL, NULL,
		new_str, new_str_len
	    ); 
	    gtk_text_thaw(text);
	}
	else
	{
	    int new_end_pos = end_pos + new_str_len;

	    insert_pos = end_pos + new_str_len;
	    end_pos = -1;
	    new_str_len = strlen(new_str); 
	    new_end_pos += new_str_len;

	    gtk_text_set_point(text, insert_pos);
	    gtk_text_freeze(text);
	    gtk_text_insert(
		text, font, NULL, NULL,
		new_str, new_str_len  
	    );
	    gtk_text_thaw(text);
	}

	EDITOR_FMT_CB_POST
}

/*
 *	Line break callback.
 */
void EditorFmtLineBreakCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "<br>";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Ampersand callback.
 */
void EditorFmtAmpersandCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "&amp;";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *      Less than callback.
 */
void EditorFmtLessThanCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "&lt;";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,   
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Greater than callback.
 */
void EditorFmtGreaterThanCB(GtkWidget *widget, gpointer data)
{
	const char *new_str = "&gt;";
	int new_str_len;
	EDITOR_FMT_CB_PRE

	new_str_len = strlen(new_str);

	gtk_text_set_point(text, insert_pos);
	gtk_text_freeze(text);
	gtk_text_insert(
	    text, font, NULL, NULL,
	    new_str, new_str_len
	);
	gtk_text_thaw(text);

	EDITOR_FMT_CB_POST
}

/*
 *	Strip tags from selected text callback.
 */
void EditorStripTagsCB(GtkWidget *widget, gpointer data)
{
	gint i, j, c, len, made_strip, strip_loss = 0;
	EDITOR_FMT_CB_PRE

	/* No selection? */
	if(end_pos < 0)
	    return;

	len = end_pos - insert_pos;
	if(len <= 0)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No Text Selected",
"No text selected to strip tags from.",
"You have not selected any text in which to strip\n\
tags from. If you wish to strip tags from the\n\
entire section then you must first go to\n\
edit->select all and then strip tags otherwise\n\
use the pointer to select the section of text you\n\
want to strip tags from and then strip tags.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}

	do
	{
	    made_strip = 0;
	    end_pos = MIN(end_pos, gtk_text_get_length(text));

	    /* Iterate from insert position to ending position */
	    for(i = insert_pos, len = 0; i < end_pos; i++)
	    {
		c = GTK_TEXT_INDEX(text, i);

		/* Is character a tag deliminator start? */
		if(c == '<')
		{
		    len++;	/* Count deliminator start char */

		    /* Seek to ending tag deliminator (if any) */
		    for(j = i + 1; j < end_pos; j++)
		    {
			len++;	/* Count this char regardless */

			/* Begin calculating length */
			c = GTK_TEXT_INDEX(text, j);
			if(c == '>')
			    break;
		    }
		    /* No ending tag found? */
		    if(j >= end_pos)
			len = 0;

		    break;
		}
	    }

	    /* Got a tag? */
	    if((i < end_pos) && (len > 0))
	    {
		/* Call text delete callback before deleting */
		EditorTextDeleteCB(
		    editable, i, i + len, editor
		);

		/* Delete characters specified by len */
		gtk_text_freeze(text);
		gtk_text_set_point(text, i);
		gtk_text_forward_delete(text, len);
		gtk_text_thaw(text);

		made_strip = 1;
		strip_loss += len;
	    }

	    /* Need to reduce end position */
	    end_pos -= len;

	} while(made_strip);


	EditorUpdateMenus(editor);
	EditorSetStatusMessage(editor, "Strip tags done");
}

#undef EDITOR_FMT_CB_POST
#undef EDITOR_FMT_CB_PRE
#undef EDITOR_GET_TEXT_WIDGET


/*
 *	Maps the Find In Pages dialog.
 */
void EditorMapFIPCB(GtkWidget *widget, gpointer data)
{
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	EditorFIPMap(editor->fip_dlg);

	return;
}


/*
 *	Find bar find button callback.
 */
void EditorFindBarFindCB(GtkWidget *widget, gpointer data)
{
	gchar *value;
	GtkCombo *find_combo;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	find_combo = (GtkCombo *)editor->find_combo;
	if(find_combo == NULL)
	    return;

	value = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(find_combo->entry)
	));

	/* Call combo widget to set new value and activate, this will
	 * then call EditorFindBarFindActivateCB()
	 */
	GUIComboActivateValue(GTK_WIDGET(find_combo), value);

	g_free(value);
}

/*
 *	Find bar find combo box activate callback.
 */
void EditorFindBarFindActivateCB(GtkWidget *widget, gpointer data)
{
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))                    
	    return;

	EditorSetBusy(editor);

	EditorDoFindCB(
	    editor,
	    (GtkCombo *)editor->find_combo,
	    FALSE
	);

	EditorUpdateMenus(editor);
	EditorSetReady(editor);
}

/*
 *	Find bar replace button callback.
 */
void EditorFindBarReplaceCB(GtkWidget *widget, gpointer data)
{
	gchar *value;
	GtkCombo *replace_combo;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	replace_combo = (GtkCombo *)editor->replace_combo;
	if(replace_combo == NULL)
	    return;

	value = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(replace_combo->entry)
	));

	/* Call combo widget to set new value and activate, this will
	 * then call EditorFindBarReplaceActivateCB()
	 */
	GUIComboActivateValue(GTK_WIDGET(replace_combo), value);

	g_free(value);
}

/*
 *	Find bar replace all button callback.
 *
 *	Note this function is a bit different than the other find and
 *	replace callback functions, it does all the setting up of values
 *	and calling procedure functions by itself.
 */
void EditorFindBarReplaceAllCB(GtkWidget *widget, gpointer data)
{
	gboolean case_sensitive = FALSE;
	gint replace_count = 0;
	GtkWidget *toplevel;
	GtkText *text = NULL;
	GtkEditable *editable = NULL;
	GtkCombo *find_combo, *replace_combo;
	gchar *needle, *replacement;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	toplevel = editor->toplevel;

	/* Get selected branch's item data */
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), editor->selected_branch
	);
	if(item == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No Item Selected",
"No item selected to search and replace through.",
"You have not selected an item with text\n\
to search and replace through.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}

	/* Get pointers to combo widgets */
	find_combo = (GtkCombo *)editor->find_combo;
	replace_combo = (GtkCombo *)editor->replace_combo;

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


	/* Get values from each combo box (coppied) */
	needle = STRDUP(gtk_entry_get_text(
	    GTK_ENTRY(find_combo->entry)
	));

	replacement = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
	if(replacement != NULL)
	    replacement = strdup(replacement);

	/* Update values to combo lists */
	GUIComboAddItem(GTK_WIDGET(find_combo), needle);
	GUIComboAddItem(GTK_WIDGET(replace_combo), replacement);


	EditorSetBusy(editor);

	/* Handle by item type */
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    /* Get pointer to header text */
	    text = (GtkText *)editor->header_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

	    gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

	    /* Do replace all */
	    replace_count += EditorDoReplaceAll(
		editor, text,
		needle, replacement,
		case_sensitive
	    );

	    gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);

	    /* Did we get and replacements? */
	    if(replace_count > 0)
	    {
		gchar *buf = g_strdup_printf(
		    "Replace done, %i occurances replaced",
		    replace_count
		);
		EditorSetStatusMessage(editor, buf);
		g_free(buf);
	    }
	    else
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"String Not Found",
"The string was not found.",
		    NULL,
		    CDIALOG_ICON_SEARCH,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		EditorSetStatusMessage(editor, "Replace done");
	    }
	    break;

	  case EditorItemTypeSection:
	    /* Get pointer to section text */
	    text = (GtkText *)editor->section_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

	    gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

	    /* Do replace all */
	    replace_count += EditorDoReplaceAll(
		editor, text,
		needle, replacement,
		case_sensitive
	    );

	    gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);

	    /* Did we get and replacements? */
	    if(replace_count > 0)
	    {
		gchar *buf = g_strdup_printf(
		    "Replace done, %i occurances replaced",
		    replace_count
		);
		EditorSetStatusMessage(editor, buf);
		g_free(buf);
	    }
	    else
	    {
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"String Not Found",
"The string was not found.",
		    NULL,
		    CDIALOG_ICON_SEARCH,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		EditorSetStatusMessage(editor, "Replace done");
	    }
	    break;

	  case EditorItemTypeFile:
	    break;
	}


	/* Mark selected branch as having changes */
	EditorItemSetToplevelHasChanges(
	    editor, editor->selected_branch, TRUE
	);

	/* Update menus */
	EditorUpdateMenus(editor);

	EditorSetReady(editor);

	g_free(replacement);
	g_free(needle);
}

/*
 *	Same as EditorFindBarReplaceAllCB() except that this will
 *	replace all occurances in all header and sections for the
 *	entire selected manual page.
 *
 *	This function relies on another function
 *	EditorFindBarReplaceEntirePageIterationCB() to iterate and
 *	recurse to replace branch and child branches.
 */
void EditorFindBarReplaceEntirePageCB(GtkWidget *widget, gpointer data)
{
	gint status;
	gint replace_count = 0;
	gboolean case_sensitive = FALSE;
	GtkCombo *find_combo, *replace_combo;
	GtkCTreeNode *branch;
	GtkWidget *toplevel;
	gchar *needle, *replacement;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	toplevel = editor->toplevel;

	/* Confirm before replace */
	CDialogSetTransientFor(toplevel);
	status = CDialogGetResponse(
"Confirm Replace Entire Page",
"You will not be able to undo this operation,\n\
are you sure you want to find and replace the\n\
entire selected Manual Page?",
	    NULL,
	    CDIALOG_ICON_WARNING,
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_CANCEL,
	    CDIALOG_BTNFLAG_CANCEL
	);
	CDialogSetTransientFor(NULL);
	switch(status)
	{
	  case CDIALOG_RESPONSE_NOT_AVAILABLE:
	  case CDIALOG_RESPONSE_CANCEL:
	  case CDIALOG_RESPONSE_NO:
	    return;
	    break;

	  case CDIALOG_RESPONSE_YES_TO_ALL:
	  case CDIALOG_RESPONSE_YES:
	    break;
	}

	/* Get selected branch */
	branch = editor->selected_branch;
	/* If not NULL, get toplevel parent branch */
	if(branch != NULL)
	   branch = EditorItemGetToplevel(editor, branch);
	if(branch == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No Manual Page Selected",
"No manual page selected to search and replace through.",
"You have not selected a manual page to search and\n\
replace through.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}

	/* Get pointers to combo widgets */
	find_combo = (GtkCombo *)editor->find_combo;
	replace_combo = (GtkCombo *)editor->replace_combo;

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

	/* Get values from each combo box (coppied) */
	needle = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
	if(needle != NULL)
	    needle = strdup(needle);

	replacement = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
	if(replacement != NULL)
	    replacement = strdup(replacement);

	/* Update values to combo lists */
	GUIComboAddItem(GTK_WIDGET(find_combo), needle);
	GUIComboAddItem(GTK_WIDGET(replace_combo), replacement);                

	/* Begin search and replace all occurances through all
	 * child branches.
	 */
	EditorSetBusy(editor);

	replace_count += EditorFindBarReplaceEntirePageIterationCB(
	    editor, branch, needle, replacement, case_sensitive
	);
	if(replace_count > 0)
	{
	    gchar *buf = g_strdup_printf(
		"Replace done, %i occurances replaced",
		replace_count
	    );
	    EditorSetStatusMessage(editor, buf);
	    g_free(buf);
	}
	else
	{
	    EditorSetStatusMessage(
		editor,
		"Replace done, no occurances found"
	    );
	}

	/* Mark selected branch as having changes */
	EditorItemSetToplevelHasChanges(
	    editor, editor->selected_branch, TRUE
	);

	/* Update menus */
	EditorUpdateMenus(editor);

	EditorSetReady(editor);

	/* Delete find buffers */
	g_free(replacement);
	g_free(needle);
}

/*
 *	Replace entire page iteration callback, called by function
 *	EditorFindBarReplaceEntirePageCB() to replace all occurances
 *	on branch and recurse into child branch.
 *
 *	The editor's selected branch will be updated then force selected
 *	to the given branch.
 *
 *	Inputs assumed valid, returns the number of replacements.
 */
static int EditorFindBarReplaceEntirePageIterationCB(
	editor_struct *editor, GtkCTreeNode *branch,
	char *needle, const char *replacement,
	gboolean case_sensitive
)
{
	gint replace_count = 0;
	GtkText *text = NULL;
	GtkEditable *editable = NULL;
	GtkCTreeRow *branch_row;
	editor_item_struct *item;


	if(branch == NULL)
	    return(replace_count);

	/* Note, make sure editor is not set as processing, otherwise
	 * we won't be able to apply and fetch values.
	 */

	/* Force select given branch */
	EditorBranchSelect(editor, branch);


	/* Begin calling replace all procedures */

	EditorSetBusy(editor);

	/* Get selected branch's item data */
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), branch
	);
	switch((item != NULL) ? item->type : -1)
	{
	  case -1:
	    break;

	  case EditorItemTypeHeader:
	    /* Get pointer to header text */
	    text = (GtkText *)editor->header_text;
	    if(text == NULL)
		break;
	    else
		editable = GTK_EDITABLE(text);

	    gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

	    /* Do replace all */   
	    replace_count += EditorDoReplaceAll(
		editor, text,
		needle, replacement,  
		case_sensitive
	    );

	    gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);
	    break;

	  case EditorItemTypeSection:
	    /* Get pointer to section text */
	    text = (GtkText *)editor->section_text;
	    if(text == NULL)
		break;
	    else
		editable = GTK_EDITABLE(text);

	    gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

	    /* Do replace all */
	    replace_count += EditorDoReplaceAll(
		editor, text,
		needle, replacement,
		case_sensitive
	    );

	    gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);
	    break;

	  case EditorItemTypeFile:
	    break;
	}

	EditorSetReady(editor);


	/* Iterate through child branches */
	branch_row = GTK_CTREE_ROW(branch);
	branch = ((branch_row == NULL) ? NULL : branch_row->children);
	while(branch != NULL)
	{
	    replace_count += EditorFindBarReplaceEntirePageIterationCB(
		editor, branch, needle, replacement, case_sensitive
	    );

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

	return(replace_count);
}


/*
 *	Find bar replace combo box activate callback.
 */
void EditorFindBarReplaceActivateCB(GtkWidget *widget, gpointer data)
{
	gboolean case_sensitive = FALSE;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

	/* Need to update the last item on the find combo */
	if(editor->find_combo != NULL)
	{
	    GtkCombo *combo = (GtkCombo *)editor->find_combo;
	    gchar *strptr;

	    /* Get value from combo's entry widget and duplicate
	     * the value if it is not NULL.
	     */

	    strptr = gtk_entry_get_text(GTK_ENTRY(combo->entry));
	    if(strptr != NULL)
		strptr = strdup(strptr);

	    GUIComboAddItem(GTK_WIDGET(combo), strptr);

	    /* Free value which we duplicated */
	    g_free(strptr);
	}

	EditorSetBusy(editor);

	EditorDoReplaceCB(
	    editor,
	    (GtkCombo *)editor->find_combo,
	    (GtkCombo *)editor->replace_combo,
	    case_sensitive
	);

	EditorUpdateMenus(editor);
	EditorSetReady(editor);
}


/*
 *	Front end for find callbacks to perform find procedure.
 */
static void EditorDoFindCB(
	editor_struct *editor, GtkCombo *find_combo, gboolean case_sensitive
)
{
	gboolean got_match, search_wrapped;
	editor_item_struct *item;
	GtkWidget *toplevel;
	GtkText *text;
	GtkEditable *editable;
	gint start_pos;
	gchar *strptr, *haystack = NULL, *needle = NULL;


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

	toplevel = editor->toplevel;

	/* Get selected branch's item data */
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), editor->selected_branch
	);
	if(item == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No Item Selected",
"No item selected to search through.",
"You have not selected an item with text\n\
to search through.",
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}


	/* Get needle buffer (coppied) */
	strptr = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
	if(strptr != NULL)
	    needle = strdup(strptr);
	else
	    needle = NULL;


	/* Handle by item type */
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    /* Get pointer to header text */
	    text = (GtkText *)editor->header_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

	    /* Get haystack from header 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 = EditorDoFind(
		editor, 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);
	    }
	    break;

	  case EditorItemTypeSection:
	    /* Get pointer to section text */
	    text = (GtkText *)editor->section_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

	    /* Get haystack from section 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 = EditorDoFind(
		editor, 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);
	    }
	    break;

	  case EditorItemTypeFile:
	    break;
	}

	g_free(haystack);
	g_free(needle);
}

/*
 *	Front end for replace callbacks to perform find and replace
 *	procedure.
 */
static void EditorDoReplaceCB(
	editor_struct *editor,
	GtkCombo *find_combo, GtkCombo *replace_combo,
	gboolean case_sensitive
)
{
	gboolean made_replace;
	editor_item_struct *item;
	GtkWidget *toplevel;
	GtkText *text;
	GtkEditable *editable;
	gint start_pos;
	gchar	*strptr, *haystack = NULL, *needle = NULL,
		*replacement = NULL;


	if((editor == NULL) || (find_combo == NULL) ||
	   (replace_combo == NULL)
	)
	    return;

	toplevel = editor->toplevel;

	/* Get selected branch's item data */
	item = EditorBranchGetData(
	    GTK_CTREE(editor->layout_ctree), editor->selected_branch
	);
	if(item == NULL)
	{
	    CDialogSetTransientFor(toplevel);
	    CDialogGetResponse(
"No Item Selected",
"No item selected to search and replace through.",
		NULL,
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}

	/* Get needle buffer (coppied) */
	strptr = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
	if(strptr != NULL)
	    needle = strdup(strptr); 
	else
	    needle = NULL;

	/* Get replacement (coppied) */
	strptr = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
	if(strptr != NULL)
	    replacement = strdup(strptr);
	else
	    replacement = NULL;


	/* Handle by item type */
	switch(item->type)
	{
	  case EditorItemTypeHeader:
	    /* Get pointer to header text */
	    text = (GtkText *)editor->header_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

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

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

	    /* Do replace */
	    made_replace = EditorDoReplace(
		editor, text,
		haystack, needle, replacement,
		(gint)gtk_text_get_length(text), start_pos,
		case_sensitive
	    );
	    /* Did we make a replacement? */
	    if(made_replace)
	    {

	    }
	    else
	    {
		/* No replacement, implies search string not found */
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"String Not Found",
"The string was not found.",
		    NULL,
		    CDIALOG_ICON_SEARCH,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    break;

	  case EditorItemTypeSection:
	    /* Get pointer to section text */
	    text = (GtkText *)editor->section_text;
	    if(text != NULL)
		editable = GTK_EDITABLE(text);
	    else
		break;

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

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

	    /* Do replace */
	    made_replace = EditorDoReplace(
		editor, text,
		haystack, needle, replacement,
		(gint)gtk_text_get_length(text), start_pos,
		case_sensitive
	    );
	    /* Did we make a replacement? */
	    if(made_replace)
	    {

	    }
	    else
	    {
		/* No replacement, implies search string not found */
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
"String Not Found",
"The string was not found.",
		    NULL,
		    CDIALOG_ICON_SEARCH,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    break;

	  case EditorItemTypeFile:
	    break;
	}

	/* Mark selected branch as having changes */
	EditorItemSetToplevelHasChanges(
	    editor, editor->selected_branch, TRUE
	);

	g_free(replacement);
	g_free(haystack);
	g_free(needle);
}


/*
 *	Updates the position relative to be just under the given
 *	widget as data.
 */
static void EditorMenuMapPositionCB(
	GtkMenu *menu, gint *x, gint *y, gpointer data
)
{
	gint rx, ry;
	GtkWidget *rel_widget = (GtkWidget *)data;
	if((menu == NULL) || (rel_widget == NULL))
	    return;

	if(!GTK_WIDGET_NO_WINDOW(rel_widget))
	{
	    GUIGetWindowRootPosition(
		rel_widget->window,
		&rx, &ry
	    );

	    if(x != NULL)
		*x = rx;
	    if(y != NULL)
		*y = ry + rel_widget->allocation.height;
	}
}

/*
 *	Right-click menu mapping.
 */
gint EditorMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	GtkWidget *w;
	GdkEventButton *button;
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (event == NULL) || (widget == NULL))
	    return(TRUE);

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

	/* Layout ctree? */
	if(widget == editor->layout_ctree)
	{
	    w = editor->layout_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 != editor->selected_branch)
			)
			{
			    EditorBranchSelect(editor, branch);
			}
		    }

		    /* Map menu */
		    gtk_menu_popup(
			GTK_MENU(w),
			NULL, NULL, NULL, editor,
			button->button, button->time
		    );
		}
	    }
	}
	/* Edit panel text widgets */
	else if((widget == editor->header_text) ||
		(widget == editor->section_text)
	)
	{
	    w = editor->edit_panel_menu;
	    if((button != NULL) && (w != NULL))
	    {
		/* Call key press event for text widget since position
		 * of cursor may have changed.
		 */
		EditorTextKeyEventCB(
		    (GtkEditable *)widget, NULL, data
		);

		if(button->button == 3)
		{
		    /* Map menu */
		    gtk_menu_popup(
			GTK_MENU(w),
			NULL, NULL, NULL, editor,
			button->button, button->time
		    );

		    /* Need to mark the text widget's button as 0 or
		     * else it will keep marking.
		     */
		    GTK_TEXT(widget)->button = 0;
		    gtk_grab_remove(widget);
		}
	    }
	}

	return(TRUE);
}

/*
 *	Menu mapping from a button "pressed" signal callback.
 */
void EditorButtonMenuMapCB(GtkButton *button, gpointer data)
{
	GtkWidget *w;
	editor_struct *editor = EDITOR(data);
	if((button == NULL) || (editor == NULL))
	    return;

#define DO_BUTTON_PRESSED	\
{ \
 if(button != NULL) \
 { \
  gtk_grab_remove(GTK_WIDGET(button)); \
  \
  while(gtk_events_pending() > 0) \
   gtk_main_iteration(); \
  \
  button->in_button = 1; \
  button->button_down = 1; \
  gtk_signal_emit_by_name(GTK_OBJECT(button), "pressed"); \
 } \
}
	/* New menu button */
	if(GTK_WIDGET(button) == editor->new_btn)
	{
	    w = editor->new_btn_new_menu;
	    if(w != NULL)
	    {
		/* Map menu */
		gtk_menu_popup(
		   GTK_MENU(w),
		   NULL, NULL,
		   EditorMenuMapPositionCB, button,
		   1, GDK_CURRENT_TIME
		);
		DO_BUTTON_PRESSED
	    }
	}
	/* Find menu button */
	else if(GTK_WIDGET(button) == editor->find_btn)
	{
	    w = editor->find_bar_find_menu;
	    if(w != NULL)
	    {
		/* Map menu */
		gtk_menu_popup(
		   GTK_MENU(w),
		   NULL, NULL,
		   EditorMenuMapPositionCB, button,
		   1, GDK_CURRENT_TIME
		);
		DO_BUTTON_PRESSED
	    }
	}
	/* Replace menu button */
	else if(GTK_WIDGET(button) == editor->replace_btn)
	{
	    w = editor->find_bar_replace_menu;
	    if(w != NULL)
	    {
		/* Map menu */
		gtk_menu_popup(
		   GTK_MENU(w),
		   NULL, NULL,
		   EditorMenuMapPositionCB, button,
		   1, GDK_CURRENT_TIME
		);
		DO_BUTTON_PRESSED
	    }
	}

#undef DO_BUTTON_PRESSED
}

/*
 *	Menu hide callback.
 *
 *	Used for buttons which map menus, when the menu for a button
 *	unmaps (hides) then function handles the "hide" signal and sets
 *	the associated button released by emitting the "released"
 *	signal.
 */
void EditorMenuHideCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *w;
	editor_struct *editor = EDITOR(data);
	if((widget == NULL) || (editor == NULL))
	    return;

#define DO_BUTTON_RELEASE	{	\
 if(w != NULL) {			\
  GTK_BUTTON(w)->in_button = 0;		\
  gtk_signal_emit_by_name(GTK_OBJECT(w), "released"); \
 }					\
}

	/* New menu? */
	if(widget == editor->new_btn_new_menu)
	{
	    w = editor->new_btn;
	    DO_BUTTON_RELEASE
	}
	/* Find menu? */
	else if(widget == editor->find_bar_find_menu)
	{
	    w = editor->find_btn;
	    DO_BUTTON_RELEASE
	}
	/* Replace menu? */
	else if(widget == editor->find_bar_replace_menu)
	{
	    w = editor->replace_btn;
	    DO_BUTTON_RELEASE
	}

#undef DO_BUTTON_RELEASE
}


/*
 *	Layout ctree expand/collapse callback.
 */
void EditorLayoutCTreeDoExpandCB(GtkWidget *widget, gpointer data)
{
	GtkCTree *ctree;
	GtkCTreeNode *branch; 
	GtkCTreeRow *branch_row;
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return;

	ctree = GTK_CTREE(editor->layout_ctree);
	branch = editor->selected_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);
}


/*
 *	Layout ctree select row callback.
 */
void EditorLayoutCTreeSelectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
	gint row;
	GtkCList *clist;
	editor_item_struct *item;
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (ctree == NULL))
	    return;

	if(GTK_WIDGET(ctree) == editor->layout_ctree)
	{
	    clist = (GtkCList *)ctree;

	    EditorSetBusy(editor);       

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

	    /* Update selected branch */
	    editor->selected_branch = branch;

	    /* Set DND drag icon */
	    EditorDNDSetIcon(
		editor, 
		EditorBranchGetData(ctree, branch)
	    );

	    /* Scroll if row not visibile */
	    row = gtk_clist_find_row_from_data(
		clist,
		EditorBranchGetData(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 */
	    if(branch != NULL)
	    {
		gchar *buf;

		/* Procedure to fetch values from the given branch
		 * then updates the editor's panel widgets and menus.
		 */
		EditorDoFetchValues(editor, branch);

		/* Notify about selection */
		item = EditorBranchGetData(
		    (GtkCTree *)clist, branch
		);
		if(item != NULL)
		{
		    switch(item->type)
		    {
		      case EditorItemTypeFile:
			buf = g_strdup_printf(
			    "Selected manual page \"%s\"",
			    item->name
			);
			EditorSetStatusMessage(editor, buf);
			g_free(buf);
			break;

		      case EditorItemTypeHeader:
			EditorSetStatusMessage(editor, "Selected header");
			break;

		      case EditorItemTypeSection:
			buf = g_strdup_printf(
			    "Selected section \"%s\"",
			    item->section_name
			);
			EditorSetStatusMessage(editor, buf);
			g_free(buf);
			break;

		      default:
			EditorSetStatusMessage(editor, "Selected unsupported type");
			break;
		    }
		}
		else
		{
		    EditorSetStatusMessage(editor, "");
		}
	    }
	    else
	    {
		EditorSetStatusMessage(editor, "");
	    }

	    EditorSetReady(editor);
	}
}

/*
 *	Layout ctree unselect row callback.
 */
void EditorLayoutCTreeUnselectCB(
	GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (ctree == NULL))
	    return;

	if(GTK_WIDGET(ctree) == editor->layout_ctree)
	{
	    /* Apply values from the editor's widgets and place them
	     * into the just unselected branch.
	     */
	    EditorDoApplyValues(editor, branch);

	    if(branch == editor->selected_branch)
		editor->selected_branch = NULL;

	    /* Clear values on editor's editing widgets, this is so
	     * user does not type in something into those widgets
	     * while no branch is selected (thus any changes would be
	     * lost when a branch gets selected again)
	     */
	    EditorDoClearValues(editor);

	    /* Clear undo/redo list on editor, this is so that they can't
	     * get applied by the user on the newly selected branch's
	     * data
	     */
	    EditorResetUndos(editor);
	}
}

/*
 *	Editor layout ctree branch expand (and collapse) callback, called
 *	whenever a branch is expanded or collapsed.
 */
void EditorLayoutCTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
)
{
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (ctree == NULL))
	    return;
 
	if(GTK_WIDGET(ctree) == editor->layout_ctree)
	{
	    /* Update layout clist column width */
	    gtk_clist_set_column_width(
		(GtkCList *)ctree,
		0,			/* Column */
		gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
	    );

	}
}


/*
 *	Syntax highlight timeout callback.
 *
 *	For performing syntax highlighting a delayed time after text has
 *	changed or been deleted on the section_text widget.
 *
 *	If editor is currently processing then this operation will not be
 *	performed.
 *
 *	This operation will only be performed once, the value of the
 *	editor's synhl_timeout_cb_id will be set to (guint)(-1) after
 *	this function has been called and this function always returns
 *	FALSE to indicate this function is not to be called again
 *	unless we add a new timeout callback.
 */
gint EditorSyntaxHighlightTimeoutCB(gpointer data)
{
	editor_struct *editor = EDITOR(data);
	if(editor == NULL)
	    return(FALSE);

	if(editor->freeze_count > 0)
	    return(FALSE);

	editor->freeze_count++;

	/* Reset synhl_timeout_cb_id, since this function will not be
	 * called again untill we add a new timeout callback.
	 */
	editor->synhl_timeout_cb_id = 0;

	/* Syntax highlighting */
	EditorSyntaxHighlight(
	    editor, editor->section_text,
	    editor->synhl_timeout_cb_cursor_pos,
	    editor->synhl_timeout_cb_start_pos,
	    editor->synhl_timeout_cb_end_pos
	);

	editor->synhl_timeout_cb_cursor_pos = -1;
	editor->synhl_timeout_cb_start_pos = -1;
	editor->synhl_timeout_cb_end_pos = -1;

	editor->freeze_count--;

	return(FALSE);
}


/*
 *	Editor GtkText widget key event callback, this is to check
 *	when the cursor has moved so that the position value on the
 *	status bar is properly set.
 *
 *	This function always returns FALSE.
 */
gint EditorTextKeyEventCB(
	GtkEditable *editable, GdkEventKey *key, gpointer data
)
{
	int c, i, row, column;
	int cur_pos, length;
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (editable == NULL))
	    return(TRUE);

	/* Get cursor position */
	cur_pos = (gint)gtk_editable_get_position(editable);

	/* Get length of buffer */
	length = (gint)gtk_text_get_length(GTK_TEXT(editable));

	if(cur_pos >= length)
	    cur_pos = length - 1;
	if(cur_pos < 0)
	    cur_pos = 0;

	for(i = 0, row = 0, column = 0; i < cur_pos; i++)
	{
	    c = GTK_TEXT_INDEX(GTK_TEXT(editable), i);
	    if((gchar)c == '\n')
	    {
		column = 0;
		row++;
	    }
	    else
	    {
		column++;
	    }
	}

	EditorSetStatusPosition(editor, row, column);

	return(TRUE);
}


/*
 *	Text change callback.
 */
void EditorTextChangeCB(GtkEditable *editable, gpointer data)
{
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (editable == NULL))
	    return;


	return;
}

/*
 *	Editor text widget insert text callback.
 */
void EditorTextInsertCB(
	GtkEditable *editable, const gchar *text, gint length,
	gint *position, gpointer data
)
{
	int i;
	gint start_pos, end_pos, text_len;
	gpointer u;
	editor_undo_remove_chars_struct *u_remove;
	GtkWidget *w;
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (editable == NULL) || (position == NULL))
	    return;

	/* Do not process this signal if editor is processing an undo or
	 * redo since this signal may be called during the undo process.
	 */
	if(editor_processing_undo == editor)
	    return;

	w = GTK_WIDGET(editable);

	/* Get positions */
	start_pos = (*position);
	end_pos = start_pos + length;

	/* Get text length */
	text_len = end_pos - start_pos;
	if(text_len < 0)
	    text_len *= -1;  

	/* Sanitize positions */
	if(end_pos < start_pos)
	{
	    gint tmp_pos = end_pos;
	    end_pos = start_pos;
	    start_pos = tmp_pos;
	}

	/* No text length? */
	if(text_len == 0)
	    return;

	/* Sanitize total */
	if(editor->total_undos < 0)
	    editor->total_undos = 0;

/* Macro to allocate a new undo structure of type EDITOR_UNDO_REMOVE_CHARS
 * on the editor->undo list. Uses variables i, u, editor, w,
 * start_pos, and u_remove. Will return this function if there was an
 * error and u_remove will be set properly to the new structure.
 */
#define DO_NEW_UNDO_REMOVE_STRUCT	\
{ \
 i = editor->total_undos; \
 editor->total_undos = i + 1; \
 editor->undo = (gpointer *)g_realloc( \
  editor->undo, \
  editor->total_undos * sizeof(gpointer) \
 ); \
 if(editor->undo == NULL) \
 { \
  editor->total_undos = 0; \
  return; \
 } \
 /* Shift */ \
 for(i = editor->total_undos - 1; i > 0; i--) \
  editor->undo[i] = editor->undo[i - 1]; \
 /* Add new struct to beginning of list */ \
 u = EditorUndoNew( \
  editor, EDITOR_UNDO_REMOVE_CHARS, \
  "Insert Characters" \
 ); \
 editor->undo[0] = u;   /* Record new struct to undo list */ \
 \
 /* Set initial values for new structure if possible */ \
 u_remove = (editor_undo_remove_chars_struct *)u; \
 if(u_remove != NULL) \
 { \
  u_remove->w = w; \
  u_remove->position = start_pos; \
 } \
}

	/* Get first undo structure, allocate as needed if it is NULL
	 * or is not of type EDITOR_UNDO_REMOVE_CHARS.
	 */
	if(editor->total_undos > 0)
	    u = editor->undo[0];
	else
	    u = NULL;
	u_remove = (editor_undo_remove_chars_struct *)u;
	if((u == NULL) ? 1 : ((*(int *)u) != EDITOR_UNDO_REMOVE_CHARS))
	{
	    DO_NEW_UNDO_REMOVE_STRUCT
	    if(u_remove == NULL)
		return;

	    EditorItemSetToplevelHasChanges(
		editor, editor->selected_branch, TRUE
	    );
	    EditorUpdateMenus(editor);
	}
	u = NULL;       /* Don't use u past this point */

	/* Sanitize length on undo structure */
	if(u_remove->length < 0)
	    u_remove->length = 0;

	/* Are the new positions contiguous? */
	if((u_remove->position + u_remove->length) == start_pos)
	{
	    u_remove->length += text_len;
	}
	else
	{
	    /* New segment being inserted, need to allocate new undo
	     * structure.
	     */
	    DO_NEW_UNDO_REMOVE_STRUCT
	    if(u_remove == NULL)
		return;

	    u_remove->position = start_pos;
	    u_remove->length = text_len;

	    EditorItemSetToplevelHasChanges(
		editor, editor->selected_branch, TRUE
	    );
	    EditorUpdateMenus(editor);
	}


	/* Remove excess undo structures */
	if(editor->total_undos > editor->max_undos)
	{
	    int max = MAX(editor->max_undos, 0);

	    /* Delete all undo structures greater than the max */
	    for(i = editor->total_undos - 1; i >= max; i--)
		EditorUndoDelete(editor, editor->undo[i]);

	    /* Update total and reallocate pointer array */
	    editor->total_undos = max;
	    if(editor->total_undos > 0)
	    {
		editor->undo = (gpointer *)g_realloc(
		    editor->undo,
		    editor->total_undos * sizeof(gpointer)
		);
		if(editor->undo == NULL)
		    editor->total_undos = 0;
	    }   
	    else
	    {
		g_free(editor->undo);
		editor->undo = NULL;
		editor->total_undos = 0;
	    }
	}


	/* Mark currently selected branch item data as having changes */
	EditorItemSetToplevelHasChanges(
	    editor, editor->selected_branch, TRUE
	);

#undef DO_NEW_UNDO_REMOVE_STRUCT

	/* If this is the editor's section_text widget then add a timeout
	 * callback for syntax highlighting.
	 */
	if(GTK_WIDGET(editable) == editor->section_text)
	{
	    /* Cancel previous syntax highlighting as needed */
	    if(editor->synhl_timeout_cb_id != 0)
	    {
		gtk_timeout_remove(editor->synhl_timeout_cb_id);
		editor->synhl_timeout_cb_id = 0;
	    }

	    /* Set cursor position as needed and if less than */
	    if(editor->synhl_timeout_cb_cursor_pos < 0)
		editor->synhl_timeout_cb_cursor_pos = start_pos;
	    else
		editor->synhl_timeout_cb_cursor_pos = MIN(
		    editor->synhl_timeout_cb_cursor_pos,
		    start_pos
		);

	    /* Set start position as needed and if less than */
	    if(editor->synhl_timeout_cb_start_pos < 0)
		editor->synhl_timeout_cb_start_pos = start_pos;
	    else
		editor->synhl_timeout_cb_start_pos = MIN(
		    editor->synhl_timeout_cb_start_pos,
		    start_pos
		);

	    /* Set end position as needed and if greater than */
	    if(editor->synhl_timeout_cb_end_pos < 0)
		editor->synhl_timeout_cb_end_pos = end_pos;
	    else
		editor->synhl_timeout_cb_end_pos = MAX(
		    editor->synhl_timeout_cb_end_pos,
		    end_pos
		);

	    /* Add new timeout callback for syntax highlighting */
	    editor->synhl_timeout_cb_id = gtk_timeout_add(
		500,
		EditorSyntaxHighlightTimeoutCB,
		editor
	    );
	}
}

/*
 *	Editor text widget delete text callback.
 */
void EditorTextDeleteCB(
	GtkEditable *editable, gint start_pos, gint end_pos, gpointer data
)
{
	int i;
	gint text_len;
	gchar *text_buf;
	GtkWidget *w;
	gpointer u;
	editor_undo_insert_chars_struct *u_insert;
	editor_struct *editor = EDITOR(data);
	if((editor == NULL) || (editable == NULL))
	    return;

	/* Do not process this signal if editor is processing an undo or
	 * redo since this signal may be called during the undo process.
	 */
	if(editor_processing_undo == editor)
	    return;

	w = GTK_WIDGET(editable);


	/* Get text length */
	text_len = end_pos - start_pos;
	if(text_len < 0)
	    text_len *= -1;

	/* Sanitize positions */
	if(end_pos < start_pos)
	{
	    gint tmp_pos = end_pos;
	    end_pos = start_pos;
	    start_pos = tmp_pos;
	}

	/* No text length? */
	if(text_len == 0)
	    return;

	/* Sanitize total */
	if(editor->total_undos < 0)
	    editor->total_undos = 0;

/* Macro to allocate a new undo structure of type EDITOR_UNDO_INSERT_CHARS
 * on the editor->undo list. Uses variables i, u, editor, w,
 * start_pos, and u_insert. Will return this function if there was an
 * error and u_insert will be set properly to the new structure.
 */
#define DO_NEW_UNDO_INSERT_STRUCT	\
{ \
 i = editor->total_undos; \
 editor->total_undos = i + 1; \
 editor->undo = (gpointer *)g_realloc( \
  editor->undo, \
  editor->total_undos * sizeof(gpointer) \
 ); \
 if(editor->undo == NULL) \
 { \
  editor->total_undos = 0; \
  return; \
 } \
 /* Shift */ \
 for(i = editor->total_undos - 1; i > 0; i--) \
  editor->undo[i] = editor->undo[i - 1]; \
 /* Add new struct to beginning of list */ \
 u = EditorUndoNew( \
  editor, EDITOR_UNDO_INSERT_CHARS, \
  "Remove Characters" \
 ); \
 editor->undo[0] = u;	/* Record new struct to undo list */ \
 \
 /* Set initial values for new structure if possible */ \
 u_insert = (editor_undo_insert_chars_struct *)u; \
 if(u_insert != NULL) \
 { \
  u_insert->w = w; \
  u_insert->position = start_pos; \
 } \
}

	/* Get first undo structure, allocate as needed if it is NULL
	 * or is not of type EDITOR_UNDO_INSERT_CHARS.
	 */
	if(editor->total_undos > 0)
	    u = editor->undo[0];
	else
	    u = NULL;
	u_insert = (editor_undo_insert_chars_struct *)u;
	if((u == NULL) ? 1 : ((*(int *)u) != EDITOR_UNDO_INSERT_CHARS))
	{
	    DO_NEW_UNDO_INSERT_STRUCT
	    if(u_insert == NULL)
		return;

	    EditorItemSetToplevelHasChanges(
		editor, editor->selected_branch, TRUE
	    );
	    EditorUpdateMenus(editor);
	}
	u = NULL;	/* Don't use u past this point */

	/* Sanitize length on undo structure */
	if(u_insert->length < 0)
	    u_insert->length = 0;

	/* Are the new positions contiguous? */
	if(u_insert->position == start_pos)
	{
	    /* Contiguous forward delete */
	    int old_len = u_insert->length;


	    /* Get characters that are about to be deleted */
	    text_buf = gtk_editable_get_chars(
		GTK_EDITABLE(w), start_pos, end_pos
	    );

	    /* Increase allocation for text on undo structure */
	    u_insert->length += text_len;
	    u_insert->characters = (gchar *)g_realloc(
		u_insert->characters,
		(u_insert->length + 1) * sizeof(gchar)
	    );
	    if(u_insert->characters == NULL)
	    {
		u_insert->length = 0;
	    }
	    else
	    {
		if(text_buf != NULL)
		    memcpy(
			&u_insert->characters[old_len],
			text_buf,
			text_len * sizeof(gchar)
		    );
		u_insert->characters[u_insert->length] = '\0';
	    } 

	    g_free(text_buf);
	    text_buf = NULL;
	}
	else if(u_insert->position == end_pos)
	{
	    /* Contiguous backward delete */
	    int old_len = u_insert->length;


	    /* Get characters that are about to be deleted */
	    text_buf = gtk_editable_get_chars(
		GTK_EDITABLE(w), start_pos, end_pos
	    );

	    /* Increase allocation for text on undo structure */
	    u_insert->length += text_len;
	    u_insert->characters = (gchar *)g_realloc(
		u_insert->characters,
		(u_insert->length + 1) * sizeof(gchar)
	    );
	    if(u_insert->characters == NULL)
	    {
		u_insert->length = 0;
	    }
	    else
	    {
		if(old_len > 0)
		    memmove(
			&u_insert->characters[text_len],
			u_insert->characters,
			old_len * sizeof(gchar)
		    );

		if(text_buf != NULL)
		    memcpy(
			u_insert->characters,
			text_buf,
			text_len * sizeof(gchar)
		    );
		u_insert->characters[u_insert->length] = '\0';
	    }

	    g_free(text_buf);
	    text_buf = NULL;

	    /* Need to update position */
	    u_insert->position = start_pos;
	}
	else
	{
	    /* New segment being deleted, need to allocate new undo
	     * structure.
	     */
	    int old_len;

	    DO_NEW_UNDO_INSERT_STRUCT
	    if(u_insert == NULL)
		return;

	    old_len = u_insert->length;

	    /* Get characters that are about to be deleted */
	    text_buf = gtk_editable_get_chars(
		GTK_EDITABLE(w), start_pos, end_pos
	    );

	    /* Increase allocation for text on undo structure */
	    u_insert->length += text_len;
	    u_insert->characters = (gchar *)g_realloc(
		u_insert->characters,
		(u_insert->length + 1) * sizeof(gchar)
	    );
	    if(u_insert->characters == NULL)
	    {
		u_insert->length = 0;
	    }
	    else
	    {
		if(text_buf != NULL)
		    memcpy(
			&u_insert->characters[old_len],
			text_buf,
			text_len * sizeof(gchar)
		    );
		u_insert->characters[u_insert->length] = '\0';
	    }

	    g_free(text_buf);
	    text_buf = NULL;

	    EditorItemSetToplevelHasChanges(
		editor, editor->selected_branch, TRUE
	    );
	    EditorUpdateMenus(editor);
	}


	/* Remove excess undo structures */
	if(editor->total_undos > editor->max_undos)
	{
	    int max = MAX(editor->max_undos, 0);

	    /* Delete all undo structures greater than the max */
	    for(i = editor->total_undos - 1; i >= max; i--)
		EditorUndoDelete(editor, editor->undo[i]);

	    /* Update total and reallocate pointer array */
	    editor->total_undos = max;
	    if(editor->total_undos > 0)
	    {
		editor->undo = (gpointer *)g_realloc(
		    editor->undo,
		    editor->total_undos * sizeof(gpointer)
		);
		if(editor->undo == NULL)
		    editor->total_undos = 0;
	    }
	    else
	    {
		g_free(editor->undo);
		editor->undo = NULL;
		editor->total_undos = 0;
	    }
	}


	/* Mark currently selected branch item data as having changes */
	EditorItemSetToplevelHasChanges(
	    editor, editor->selected_branch, TRUE
	);

#undef DO_NEW_UNDO_INSERT_STRUCT

	/* If this is the editor's section_text widget then add a timeout
	 * callback for syntax highlighting.
	 */
	if(GTK_WIDGET(editable) == editor->section_text)
	{
	    /* Cancel previous syntax highlighting as needed */
	    if(editor->synhl_timeout_cb_id != 0)
	    {
		gtk_timeout_remove(editor->synhl_timeout_cb_id);
		editor->synhl_timeout_cb_id = 0;
	    }

	    /* Set cursor position as needed and if less than */
	    if(editor->synhl_timeout_cb_cursor_pos < 0)
		editor->synhl_timeout_cb_cursor_pos = start_pos;
	    else
		editor->synhl_timeout_cb_cursor_pos = MIN(
		    editor->synhl_timeout_cb_cursor_pos,
		    start_pos
		);

	    /* Set start position as needed and if less than */
	    if(editor->synhl_timeout_cb_start_pos < 0)
		editor->synhl_timeout_cb_start_pos = start_pos;
	    else
		editor->synhl_timeout_cb_start_pos = MIN(
		    editor->synhl_timeout_cb_start_pos,
		    start_pos
		);

	    /* Set end position as needed and if greater than */
	    if(editor->synhl_timeout_cb_end_pos < 0)
		editor->synhl_timeout_cb_end_pos = end_pos;
	    else
		editor->synhl_timeout_cb_end_pos = MAX(
		    editor->synhl_timeout_cb_end_pos,
		    end_pos
		);

	    /* Add new timeout callback for syntax highlighting */
	    editor->synhl_timeout_cb_id = gtk_timeout_add(
		500,
		EditorSyntaxHighlightTimeoutCB,
		editor
	    );
	}
}
