#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <unistd.h>

#include "../../include/string.h"
#include "../../include/prochandle.h"

#include "../guiutils.h"
#include "../cdialog.h"
#include "../progressdialog.h"
#include "../fb.h"
#include "../pdialog.h"
#include "../lib/endeavour2.h"
#include "config.h"

#include "../images/icon_edit_48x48.xpm"

#include "../images/icon_print_32x32.xpm"

#include "../images/icon_new_20x20.xpm"
#include "../images/icon_open_20x20.xpm"
#include "../images/icon_save_20x20.xpm"
#include "../images/icon_save_as_20x20.xpm"
#include "../images/icon_print_20x20.xpm"
#include "../images/icon_close_20x20.xpm"
#include "../images/icon_cut_20x20.xpm"
#include "../images/icon_copy_20x20.xpm"
#include "../images/icon_paste_20x20.xpm"
#include "../images/icon_add_20x20.xpm"
#include "../images/icon_cancel_20x20.xpm"
#include "../images/icon_search_20x20.xpm"
#include "../images/icon_replace_20x20.xpm"
#include "../images/icon_about_20x20.xpm"


/*
 *	Text Editor:
 */
typedef struct {

	GtkWidget	*toplevel;
	GtkAccelGroup	*accelgrp;
	gint		busy_count,
			freeze_count;

	GdkCursor	*busy_cur,
			*text_cur;

	GtkWidget	*main_vbox,

			*menu_bar_handle,
			*cut_mi,
			*copy_mi,
			*paste_mi,
			*delete_mi,
			*select_all_mi,
			*unselect_all_mi,
			*find_mi,
			*find_next_mi,
			*replace_mi,
			*insert_file_mi,

			*text,

			*popup_menu,
			*popup_cut_mi,
			*popup_copy_mi,
			*popup_paste_mi,
			*popup_delete_mi,
			*popup_select_all_mi,
			*popup_unselect_all_mi,
			*popup_find_mi,
			*popup_find_next_mi,
			*popup_replace_mi,
			*popup_insert_file_mi;

	GtkAdjustment	*hadj,
			*vadj;

	gboolean	modified;

	gchar		*filename;

	gchar		*last_print_cmd;

	gchar		*last_find_str,
			*last_replace_str;
	gboolean	last_find_case_sensitive;

	edv_context_struct *ctx;

} tedit_struct;
#define TEDIT(p)	((tedit_struct *)(p))


static void TEditSignalCB(int s);

static gint TEditDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void TEditTextChangedCB(GtkWidget *w, gpointer data);
static gint TEditTextEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void TEditDNDDragReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
static void TEditNewTextCB(GtkWidget *widget, gpointer data);
static void TEditOpenCB(GtkWidget *widget, gpointer data);
static void TEditInsertFileCB(GtkWidget *widget, gpointer data);
static void TEditSaveCB(GtkWidget *widget, gpointer data);
static void TEditSaveAsCB(GtkWidget *widget, gpointer data);
static void TEditPrintCB(GtkWidget *widget, gpointer data);
static void TEditCloseCB(GtkWidget *widget, gpointer data);
static void TEditCutCB(GtkWidget *widget, gpointer data);
static void TEditCopyCB(GtkWidget *widget, gpointer data);
static void TEditPasteCB(GtkWidget *widget, gpointer data);
static void TEditDeleteCB(GtkWidget *widget, gpointer data);
static void TEditSelectAllCB(GtkWidget *widget, gpointer data);
static void TEditUnselectAllCB(GtkWidget *widget, gpointer data);
static void TEditFindCB(GtkWidget *widget, gpointer data);
static void TEditFindNextCB(GtkWidget *widget, gpointer data);
static void TEditReplaceCB(GtkWidget *widget, gpointer data);
static void TEditAboutCB(GtkWidget *widget, gpointer data);

static void TEditScrollToLine(tedit_struct *te, const gint line_num);
static void TEditFindQuery(tedit_struct *te);
static void TEditFindNext(tedit_struct *te);
static void TEditReplaceQuery(tedit_struct *te);
static gint TEditFindReplace(
	tedit_struct *te,
	const gchar *needle_str,
	const gchar *replace_str,
	const gboolean case_sensitive,
	const gboolean wrap_around,
	const gboolean verbose,
	gboolean *yes_to_all
);

static void TEditNewText(tedit_struct *te);
static void TEditOpen(tedit_struct *te);
static void TEditOpenFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
);
static void TEditInsertFileQuery(tedit_struct *te);
static void TEditInsertFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
);
static void TEditSave(tedit_struct *te);
static void TEditSaveAs(tedit_struct *te);
static gint TEditSaveFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
);

static void TEditPrintQuery(tedit_struct *te);
static gint TEditPrint(
        tedit_struct *te,
        const gchar *print_cmd,
	const gint ncoppies,
        const gboolean verbose
);

static void TEditClose(tedit_struct *te);

static void TEditBuildMenuBar(tedit_struct *te, GtkWidget *parent);
static void TEditBuildPopupMenu(tedit_struct *te);
static tedit_struct *TEditNew(
	const gint argc, gchar **argv,
	edv_context_struct *ctx
);
static void TEditSetBusy(tedit_struct *te, const gboolean busy);
static void TEditUpdate(tedit_struct *te);
static void TEditDelete(tedit_struct *te);


#define TEDIT_WIDTH		640
#define TEDIT_HEIGHT		480


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

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


#define DO_CHECK_CHANGES_AND_QUERY_USER(_te_) {		\
 /* Check if current data has modifications and		\
  * if so, query user to save first			\
  */							\
 if(((_te_) != NULL) ? (_te_)->modified : FALSE) {	\
  gint response;					\
							\
  EDVPlaySoundQuestion((_te_)->ctx);			\
  CDialogSetTransientFor((_te_)->toplevel);		\
  response = CDialogGetResponse(			\
"Save Changes?",					\
"The text contains changes that have not been\n\
saved, do you want to save those changes?", \
"There are changes made to the text that have\n\
not been saved. If you want to save those changes,\n\
then click on \"Yes\". If you want to discard\n\
those changes then click on \"No\". If you do not\n\
want to continue with this operation then click\n\
on \"Cancel\".",					\
   CDIALOG_ICON_WARNING,				\
   CDIALOG_BTNFLAG_YES |				\
   CDIALOG_BTNFLAG_NO |					\
   CDIALOG_BTNFLAG_CANCEL |				\
   CDIALOG_BTNFLAG_HELP,				\
   CDIALOG_BTNFLAG_YES					\
  );							\
  CDialogSetTransientFor(NULL);				\
  switch(response) {					\
   case CDIALOG_RESPONSE_YES_TO_ALL:			\
   case CDIALOG_RESPONSE_YES:				\
    TEditSave(_te_);					\
    /* Check if changes were not saved? */		\
    if((_te_)->modified)				\
     return;						\
    break;						\
							\
   case CDIALOG_RESPONSE_NO:				\
    break;						\
							\
   default:						\
    return;						\
    break;						\
  }							\
 }							\
}


/*
 *	UNIX signal handler.
 */
static void TEditSignalCB(int s)
{
	switch(s)
	{
	  case SIGINT:
	  case SIGTERM:
	  case SIGSEGV:
	    exit(1);
	    break;
	}
}

/*
 *	Text Editor toplevel GtkWindow "delete_event" callback.
 */
static gint TEditDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return(FALSE);

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

        te->freeze_count++;

        TEditClose(te);

        te->freeze_count--;

	return(TRUE);
}


/*
 *	Text changed callback.
 */
static void TEditTextChangedCB(GtkWidget *w, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	if(!te->modified)
	{
	    te->modified = TRUE;
	    TEditUpdate(te);
	}

	te->freeze_count--;
}

/*
 *	Text event signal callback.
 */
static gint TEditTextEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventKey *key;
	GdkEventButton *button;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return(status);

	if(te->freeze_count > 0)
	    return(status);

	te->freeze_count++;

	switch((gint)event->type)
	{
	  case GDK_KEY_PRESS:
	    key = (GdkEventKey *)event;
	    switch(key->keyval)
	    {
	      case GDK_a:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditSaveAs(te);
		    status = TRUE;
		}
		break;
	      case GDK_f:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditFindQuery(te);
		    status = TRUE;
		}
		break;
	      case GDK_i:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditInsertFileQuery(te);
		    status = TRUE;
		}
		break;
	      case GDK_n:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditNewText(te);
		    status = TRUE;
		}
		break;
	      case GDK_o:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditOpen(te);
		    status = TRUE;
		}
		break;
	      case GDK_p:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditPrintQuery(te);
		    status = TRUE;
		}
		break;
	      case GDK_r:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditReplaceQuery(te);
		    status = TRUE;
		}
		break;
	      case GDK_s:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditSave(te);
		    status = TRUE;
		}
		break;
	      case GDK_w:
		if(key->state & GDK_CONTROL_MASK)
		{
		    gtk_signal_emit_stop_by_name(
			GTK_OBJECT(widget),
			"key_press_event"
		    );
		    TEditClose(te);
		    status = TRUE;
		}
		break;
	    }
	    /* Update the widgets on any key press which may have
	     * changed the GtkText selection state
	     */
	    TEditUpdate(te);
	    break;

	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    /* Update the widgets on any key release which may have
	     * changed the GtkText selection state
	     */
	    TEditUpdate(te);
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case GDK_BUTTON3:
		/* Popup menu */
		gtk_menu_popup(
		    GTK_MENU(te->popup_menu),
		    NULL, NULL,
		    NULL, NULL,
		    button->button, button->time
		);
                /* Need to mark the GtkText button as 0 or else it will
                 * keep marking
                 */
                GTK_TEXT(widget)->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(te->vadj != NULL)
                {
                    GtkAdjustment *adj = te->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
                 */
                GTK_TEXT(widget)->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(te->vadj != NULL)
                {
                    GtkAdjustment *adj = te->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
                 */
                GTK_TEXT(widget)->button = 0;
                gtk_grab_remove(widget);
                gtk_signal_emit_stop_by_name(
                    GTK_OBJECT(widget), "button_press_event"
                );
                status = TRUE;
                break;
	    }
	    /* Update the widgets based on any button press which
	     * may have changed the GtkText selection state
	     */
	    TEditUpdate(te);
	    break;

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

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

	      case GDK_BUTTON5:
                /* Need to mark the GtkText button as 0 or else it will
                 * keep marking
                 */
                GTK_TEXT(widget)->button = 0;
                gtk_grab_remove(widget);
		gtk_signal_emit_stop_by_name(
		    GTK_OBJECT(widget), "button_release_event"
		);
		status = TRUE;
		break;
	    }     
	    /* Update the widgets based on any button release which
	     * may have changed the GtkText selection state
	     */
	    TEditUpdate(te);
	    break;
	}

	te->freeze_count--;

	return(status);
}



/*
 *	Text "drag_data_received" callback.
 *
 *	Opens the object specified by the url in the drag data.
 */
static void TEditDNDDragReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	GtkWidget *w, *toplevel;
	tedit_struct *te = TEDIT(data);
	if((widget == NULL) || (dc == NULL) || (te == NULL))
	    return;

	toplevel = te->toplevel;
	w = te->text;

	/* Prompt the user to save the text as needed */
	DO_CHECK_CHANGES_AND_QUERY_USER(te);

	TEditSetBusy(te, TRUE);

	/* View widget the same as the callback widget? */
	if(w == widget)
	{
	    /* Check if the DND target type is one that we support */
	    if((info == TEDIT_DND_TYPE_INFO_TEXT_PLAIN) ||
	       (info == TEDIT_DND_TYPE_INFO_TEXT_URI_LIST) ||
	       (info == TEDIT_DND_TYPE_INFO_STRING)
	    )
	    {
		const gchar	*s,
				*url = (const gchar *)selection_data->data;
		gchar *path;

		/* The DND data should be a URL string, we need to parse
		 * it and get the absolute path portion of that URL
		 * string
		 */
		s = strstr(url, "://");
		if(s != NULL)
		    s += STRLEN("://");
		else
		    s = url;

		s = strchr(s, '/');
		path = STRDUP(s);

		/* Got an absolute path to the object to be opened? */
		if(path != NULL)
		{
		    /* Open the file */
		    TEditOpenFile(te, path, TRUE);

		    /* Set the new file name */
		    if(path != te->filename)
		    {
			g_free(te->filename);
			te->filename = STRDUP(path);
		    }

		    te->modified = FALSE;

		    g_free(path);
		}
	    }
	}

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}


/*
 *	New callback.
 */
static void TEditNewTextCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditNewText(te);

	te->freeze_count--;
}

/*
 *	Open callback.
 */
static void TEditOpenCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditOpen(te);

	te->freeze_count--;
}

/*
 *	Insert file callback.
 */
static void TEditInsertFileCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditInsertFileQuery(te);

	te->freeze_count--;
}

/*
 *	Save callback.
 */
static void TEditSaveCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditSave(te);

	te->freeze_count--;
}

/*
 *	Save as callback.
 */
static void TEditSaveAsCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditSaveAs(te);

	te->freeze_count--;
}

/*
 *	Print callback.
 */
static void TEditPrintCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditPrintQuery(te);

	te->freeze_count--;
}

/*
 *	Close callback.
 */
static void TEditCloseCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditClose(te);

	te->freeze_count--;
}

/*
 *	Cut callback.
 */
static void TEditCutCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

	gtk_text_freeze(text);
	gtk_editable_cut_clipboard(editable);
	gtk_text_thaw(text);

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *      Copy callback.
 */
static void TEditCopyCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

	gtk_text_freeze(text);
	gtk_editable_copy_clipboard(editable);
	gtk_text_thaw(text);

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *      Paste callback.
 */
static void TEditPasteCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

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

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *	Delete callback.
 */
static void TEditDeleteCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

	gtk_text_freeze(text);
	gtk_editable_delete_selection(editable);
	gtk_text_thaw(text);

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *	Select All callback.
 */
static void TEditSelectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

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

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

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *	Unselect All callback.
 */
static void TEditUnselectAllCB(GtkWidget *widget, gpointer data)
{
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

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

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

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);

	te->freeze_count--;
}

/*
 *	Find callback.
 */
static void TEditFindCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditFindQuery(te);

	te->freeze_count--;
}

/*
 *      Find next callback.
 */
static void TEditFindNextCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditFindNext(te);

	te->freeze_count--;
}

/*
 *	Replace callback.
 */
static void TEditReplaceCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	if(te->freeze_count > 0)
	    return;

	te->freeze_count++;

	TEditReplaceQuery(te);

	te->freeze_count--;
}

/*
 *	About callback.
 */
static void TEditAboutCB(GtkWidget *widget, gpointer data)
{
	tedit_struct *te = TEDIT(data);
	if(te == NULL)
	    return;

	EDVAbout(te->ctx);
	EDVContextSync(te->ctx);
}


/*
 *	Scroll to the line.
 *
 *	The te specifies the Text Editor.
 *
 *	The line_num specifies the line number (starting from 0 for
 *	the first line) or -1 for the last line.
 */
static void TEditScrollToLine(tedit_struct *te, const gint line_num)
{
	gchar *buf;
	gint i, len;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	len = (gint)gtk_text_get_length(text);
	buf = gtk_editable_get_chars(editable, 0, len);
	if(buf == NULL)
	    return;

	if(line_num > -1)
	{
	    gint cur_line_num = 0;
	    gchar	*buf_ptr = buf,
			*buf_end = buf + len;

	    while(buf_ptr < buf_end)
	    {
		if(line_num == cur_line_num)
		    break;

		if(*buf_ptr == '\n')
		    cur_line_num++;

		buf_ptr++;
	    }

	    i = (gint)(buf_ptr - buf);
	}
	else
	{
	    /* Scroll to the last line */
	    gchar	*buf_end = buf + len,
			*buf_ptr = buf_end - 1;

	    while(buf_ptr >= buf)
	    {
		if(*buf_ptr == '\n')
		{
		    buf_ptr++;
		    break;
		}

		buf_ptr--;
	    }

	    if(buf_ptr < buf)
		buf_ptr = buf;

	    i = (gint)(buf_ptr - buf);
	}

	g_free(buf);

	gtk_text_freeze(text);

	/* Position the cursor after the inserted text */
	gtk_editable_set_position(editable, i);

	gtk_text_thaw(text);
}

/*
 *	Prompts the user to find a string in the text.
 */
static void TEditFindQuery(tedit_struct *te)
{
	gchar **strv;
	gint strc;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if((te == NULL) || PDialogIsQuery())
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

	PDialogDeleteAllPrompts();
 	PDialogAddPrompt(NULL, "Find:", te->last_find_str);
	PDialogAddPromptToggle(NULL, "Case Sensitive", te->last_find_case_sensitive);
	PDialogSetSize(320, -1);
	PDialogSetTransientFor(toplevel);
	strv = PDialogGetResponse(
	    "Find", NULL, NULL,
	    PDIALOG_ICON_SEARCH,
	    "Find", "Cancel",
	    PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
	    PDIALOG_BTNFLAG_SUBMIT,
	    &strc
	);
	PDialogSetTransientFor(NULL);

	if((strv != NULL) && (strc > 0))
	{
	    gboolean yes_to_all = FALSE;
	    gint nfinds;

	    /* Record the last find string */
	    if(strc > 0)
	    {
		g_free(te->last_find_str);
		te->last_find_str = STRDUP(strv[0]);
	    }
	    /* Record the last find case sensitive */
	    if(strc > 1)
	    {
		te->last_find_case_sensitive = ATOI(strv[1]);
	    }

	    /* Find */
	    nfinds = TEditFindReplace(
		te,
		te->last_find_str,
		NULL,			/* Do not replace */
		te->last_find_case_sensitive,
		TRUE,			/* Wrap around */
		TRUE,			/* Verbose */
		&yes_to_all
	    );
	}

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Find next.
 */
static void TEditFindNext(tedit_struct *te)
{
	gboolean yes_to_all = FALSE;
	gint nfinds;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

	/* Find next */
	nfinds = TEditFindReplace(
	    te,
	    te->last_find_str,
	    NULL,			/* Do not replace */
	    te->last_find_case_sensitive,
	    TRUE,			/* Wrap around */
	    TRUE,			/* Verbose */
	    &yes_to_all
	);

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Prompts the user to replace a text in the string.
 */
static void TEditReplaceQuery(tedit_struct *te)
{
	gchar **strv;
	gint strc;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if((te == NULL) || PDialogIsQuery())
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

	PDialogDeleteAllPrompts();
 	PDialogAddPrompt(NULL, "Replace:", te->last_find_str);
 	PDialogAddPrompt(NULL, "With:", te->last_replace_str);
	PDialogAddPromptToggle(NULL, "Case Sensitive", te->last_find_case_sensitive);
	PDialogSetSize(320, -1);
	PDialogSetTransientFor(toplevel);
	strv = PDialogGetResponse(
	    "Replace", NULL, NULL,
	    PDIALOG_ICON_SEARCH,
	    "Replace", "Cancel",
	    PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
	    PDIALOG_BTNFLAG_SUBMIT,
	    &strc
	);
	PDialogSetTransientFor(NULL);

	if((strv != NULL) && (strc > 0))
	{
	    gboolean yes_to_all = FALSE;
	    gint nreplaces;

	    /* Record the last find string */
	    if(strc > 0)
	    {
		g_free(te->last_find_str);
		te->last_find_str = STRDUP(strv[0]);
	    }
	    /* Record the last replace string */
	    if(strc > 1)
	    {
		g_free(te->last_replace_str);
		te->last_replace_str = STRDUP(strv[1]);
	    }
	    /* Record the last find case sensitive */
	    if(strc > 2)
	    {
		te->last_find_case_sensitive = ATOI(strv[2]);
	    }

	    /* Replace */
	    nreplaces = TEditFindReplace(
		te,
		te->last_find_str,
		te->last_replace_str,
		te->last_find_case_sensitive,
		TRUE,			/* Wrap around */
		TRUE,			/* Verbose */
		&yes_to_all
	    );
	    if(nreplaces > 0)
	    {
		if(!te->modified)
		    te->modified = TRUE;
	    }
	}

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Find or replace.
 *
 *	The te specifies the Text Editor.
 *
 *	The needle_str specifies the string to search for in the
 *	text.
 *
 *	If replace_str is not NULL then it specifies the string to
 *	replace each occurance of needle_str with. It also specifies
 *	that the operation is replace.
 *
 *	If case_sensitive is TRUE then only case sensitive matches
 *	of needle_str will be made.
 *
 *	If verbose is TRUE then any warning or error messages will
 *	be displayed.
 *
 *	The *yes_to_all specifies the current yes to all value.
 *
 *	Returns the number of matches if the operation is find,
 *	if the operation is replace then returns the number of
 *	replacements.
 */
static gint TEditFindReplace(
	tedit_struct *te,
	const gchar *needle_str,
	const gchar *replace_str,
	const gboolean case_sensitive,
	const gboolean wrap_around,
	const gboolean verbose,
	gboolean *yes_to_all
)
{
	gint noperations = 0, nfinds = 0;
	gint	len, cur_pos, start_pos, needle_len, replace_len,
		replace_and_needle_delta_len;
	gchar *buf, *buf_ptr;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if((te == NULL) || STRISEMPTY(needle_str))
	    return(noperations);

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	/* Get the lengths of the needle and replace strings */
	needle_len = STRLEN(needle_str);
	if(replace_str != NULL)
	    replace_len = STRLEN(replace_str);
	else
	    replace_len = 0;

	/* Calculate the difference between the replace string and
	 * the needle string length
	 */
	replace_and_needle_delta_len = replace_len - needle_len;

	/* Get the copy of the text from the GtkText */
	len = (gint)gtk_text_get_length(text);
	buf = gtk_editable_get_chars(editable, 0, len);
	if(buf == NULL)
	{
	    if(verbose)
	    {
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    (replace_str != NULL) ? "Replace Failed" : "Find Failed",
		    "Unable to obtain the text from the GtkText",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    return(noperations);
	}

	/* Get the current position and seek past one so that any
	 * match from a prior call does not get matched again on
	 * the first sweep
	 */
	cur_pos = editable->current_pos + 1;
	if(cur_pos < 0)
	    cur_pos = 0;
	if(cur_pos >= len)
	    cur_pos = 0;

	/* Record the starting position of the find */
	start_pos = cur_pos;

/* Sets the cursor position on the GtkText */
#define SET_CURSOR_POSITION(_pos_)	{		\
 gint i = (_pos_);					\
 if(i >= len)						\
  i = len - 1;						\
 if(i < 0)						\
  i = 0;						\
 while(gtk_events_pending() > 0)			\
  gtk_main_iteration();					\
 gtk_text_freeze(text);					\
 gtk_editable_set_position(editable, i);		\
 gtk_text_thaw(text);					\
}

/* Selects the region on the GtkText and sets the cursor position
 * to the start of the specified region
 */
#define SELECT_REGION(_start_,_end_)	{		\
 gint i = (_start_), i2 = (_end_);			\
 if(i >= len)						\
  i = len - 1;						\
 if(i < 0)						\
  i = 0;						\
 if(i2 >= len)						\
  i2 = len - 1;						\
 if(i2 < 0)						\
  i2 = 0;						\
							\
 /* Unselect the previous region */			\
 while(gtk_events_pending() > 0)			\
  gtk_main_iteration();					\
							\
 gtk_text_freeze(text);					\
 gtk_editable_select_region(editable, -1, -1);		\
 gtk_text_thaw(text);					\
							\
 /* Select the specified region */			\
 while(gtk_events_pending() > 0)			\
  gtk_main_iteration();					\
							\
 gtk_text_freeze(text);					\
 gtk_editable_set_position(editable, i);		\
 gtk_editable_select_region(editable, i, i2);		\
 gtk_text_thaw(text);					\
}

/* Updates the GtkText with the specified text */
#define UPDATE_GTK_TEXT(_buf_,_len_)	{		\
 while(gtk_events_pending() > 0)			\
  gtk_main_iteration();					\
							\
 gtk_text_freeze(text);					\
 gtk_editable_delete_text(editable, 0, -1);		\
 gtk_text_set_point(text, 0);				\
 if((_len_) > 0) {					\
  gtk_text_insert(					\
   text,						\
   NULL,						\
   NULL, NULL,						\
   (_buf_), (_len_)					\
  );							\
 }							\
 gtk_text_thaw(text);					\
}

	/* Begin the first find sweep from the cursor position to the
	 * end of the text
	 */
	while(cur_pos < len)
	{
	    /* Find the next occurance */
	    if(case_sensitive)
		buf_ptr = (gchar *)strstr(
		    (char *)(buf + cur_pos), (char *)needle_str
		);
	    else
		buf_ptr = (gchar *)strcasestr(
		    (char *)(buf + cur_pos), (char *)needle_str
		);

	    /* No further occurances? */
	    if(buf_ptr == NULL)
		break;

	    /* Set the cursor position to the start of this match */
	    cur_pos = (gint)(buf_ptr - buf);

	    /* Replace? */
	    if(replace_str != NULL)
	    {
		/* Replace */
		gint response;

		nfinds++;

		/* Yes to all? */
		if(*yes_to_all)
		{
		    /* Yes to all */
		    response = CDIALOG_RESPONSE_YES_TO_ALL;
		}
		else
		{
		    /* Select this matched occurance */
		    SELECT_REGION(cur_pos, cur_pos + needle_len);

		    /* Confirm replace */
		    EDVPlaySoundQuestion(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    response = CDialogGetResponse(
			"Confirm Replace",
			"Replace this occurance?",
			NULL,
			CDIALOG_ICON_QUESTION,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		    switch(response)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			*yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
			break;
		      case CDIALOG_RESPONSE_NO:
			break;
		      default:
			/* All else assume cancel */
			g_free(buf);
			SET_CURSOR_POSITION(start_pos - 1);
			return(noperations);
			break;
		    }
		}
		/* Okay to replace this occurance? */
		if((response == CDIALOG_RESPONSE_YES_TO_ALL) ||
		   (response == CDIALOG_RESPONSE_YES)
		)
		{
		    /* Replace this occurance */
		    buf = (gchar *)strdelchrs(
			(char *)buf, (int)cur_pos, (int)needle_len
		    );
		    buf = (gchar *)strinsstr(
			(char *)buf, (int)cur_pos, replace_str
		    );

		    /* Get the new length of the buffer */
		    len = STRLEN(buf);

		    /* Seek past the replaced occurance (if it is
		     * positive in length)
		     */
		    cur_pos += replace_len;

		    /* Update the modified text to the GtkText */
		    if(!(*yes_to_all))
			UPDATE_GTK_TEXT(buf, len);

		    noperations++;
		}
		else
		{
		    /* Do not replace, seek past this match and find
		     * the next occurance
		     */
		    cur_pos += needle_len;
		}
	    }
	    else
	    {
		/* Find
		 *
		 * Select this matched occurance on the GtkText
		 */
		SELECT_REGION(cur_pos, cur_pos + needle_len);
		cur_pos += needle_len;
		nfinds++;
		noperations++;
		break;
	    }
	}

	/* If this is a find operation and an occurance was found
	 * then return
	 */
	if((replace_str == NULL) && (noperations > 0))
	{
	    g_free(buf);
	    return(noperations);
	}

	/* Do not wrap around? */
	if(!wrap_around)
	{
	    /* Was replacing? */
	    if(replace_str != NULL)
	    {
		/* If replacing and *yes_to_all was TRUE then the
		 * text needs to be updated to the GtkText because
		 * it would not have been updated during the above
		 * sweeps if *yes_to_all was TRUE
		 */
		if(*yes_to_all)
		    UPDATE_GTK_TEXT(buf, len);

		SET_CURSOR_POSITION(start_pos - 1);
	    }

	    g_free(buf);

	    /* No finds or replaces? */
	    if((noperations == 0) && (nfinds == 0))
	    {
		if(verbose)
		{
		    EDVPlaySoundInfo(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			(replace_str != NULL) ? "Replace" : "Find",
			"The string you entered was not found",
			NULL,
			CDIALOG_ICON_INFO,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		}
	    }

	    return(noperations);
	}

	/* Begin the second find sweep from the start of the text to
	 * the start of the operation
	 */
	cur_pos = 0;
	while(cur_pos < MIN(start_pos, len))
	{
	    /* Find the next occurance */
	    if(case_sensitive)
		buf_ptr = (gchar *)strstr(
		    (char *)(buf + cur_pos), (char *)needle_str
		);
	    else
		buf_ptr = (gchar *)strcasestr(
		    (char *)(buf + cur_pos), (char *)needle_str
		);

	    /* No further occurances? */
	    if(buf_ptr == NULL)
		break;

	    /* Set the cursor position to the start of this match */
	    cur_pos = (gint)(buf_ptr - buf);

	    /* Skip if this occurance is at or past the start of the
	     * operation
	     */
	    if(cur_pos >= start_pos)
		break;

	    /* Replace? */
	    if(replace_str != NULL)
	    {
		/* Replace */
		gint response;

		nfinds++;

		/* Yes to all? */
		if(*yes_to_all)
		{
		    /* Yes to all */
		    response = CDIALOG_RESPONSE_YES_TO_ALL;
		}
		else
		{
		    /* Select this matched occurance */
		    SELECT_REGION(cur_pos, cur_pos + needle_len);

		    /* Confirm replace */
		    EDVPlaySoundQuestion(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    response = CDialogGetResponse(
			"Confirm Replace",
			"Replace this occurance?",
			NULL,
			CDIALOG_ICON_QUESTION,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		    switch(response)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			*yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
			break;
		      case CDIALOG_RESPONSE_NO:
			break;
		      default:
			/* All else assume cancel */
			g_free(buf);
			SET_CURSOR_POSITION(start_pos - 1);
			return(noperations);
			break;
		    }
		}
		/* Okay to replace this occurance? */
		if((response == CDIALOG_RESPONSE_YES_TO_ALL) ||
		   (response == CDIALOG_RESPONSE_YES)
		)
		{
		    /* Replace this occurance */
		    buf = (gchar *)strdelchrs(
			(char *)buf, (int)cur_pos, (int)needle_len
		    );
		    buf = (gchar *)strinsstr(
			(char *)buf, (int)cur_pos, replace_str
		    );

		    /* Get the new length of the buffer */
		    len = STRLEN(buf);

		    /* Seek past the replaced occurance (if it is
		     * positive in length)
		     */
		    cur_pos += replace_len;

		    /* Adjust the start position due to change of the
		     * buffer contents before the start position
		     */
		    start_pos += replace_and_needle_delta_len;
		    if(start_pos < 0)
			start_pos = 0;

		    /* Update the modified text to the GtkText */
		    if(!(*yes_to_all))
			UPDATE_GTK_TEXT(buf, len);

		    noperations++;
		}
		else
		{
		    /* Do not replace, seek past this match and find
		     * the next occurance
		     */
		    cur_pos += needle_len;
		}
	    }
	    else
	    {
		/* Find
		 *
		 * Select this matched occurance on the GtkText
		 */
		SELECT_REGION(cur_pos, cur_pos + needle_len);
		cur_pos += needle_len;
		nfinds++;
		noperations++;
		break;
	    }
	}

	/* Was replacing? */
	if(replace_str != NULL)
	{
	    /* If replacing and *yes_to_all was TRUE then the text
	     * needs to be updated to the GtkText because it would
	     * not have been updated during the above sweeps if
	     * *yes_to_all was TRUE
	     */
	    if(*yes_to_all)
		UPDATE_GTK_TEXT(buf, len);

	    SET_CURSOR_POSITION(start_pos - 1);
	}

	/* Delete the copy of the text */
	g_free(buf);

	/* No occurances? */
	if((noperations == 0) && (nfinds == 0))
	{
	    if(verbose)
	    {
		EDVPlaySoundInfo(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    (replace_str != NULL) ? "Replace" : "Find",
		    "The string you entered was not found",
		    NULL,
		    CDIALOG_ICON_INFO,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	}

	return(noperations);
#undef UPDATE_GTK_TEXT
#undef SELECT_REGION
#undef SET_CURSOR_POSITION
}


/*
 *	New.
 *
 *	Prompts the user to save the text as needed and then clears
 *	the text.
 */
static void TEditNewText(tedit_struct *te)
{
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	/* Prompt the user to save the text as needed */
	DO_CHECK_CHANGES_AND_QUERY_USER(te);

	TEditSetBusy(te, TRUE);

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

	te->modified = FALSE;

	g_free(te->filename);
	te->filename = NULL;

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Open.
 *
 *	Prompts the user to save the text as needed and then prompts
 *	the user to open a file.
 */
static void TEditOpen(tedit_struct *te)
{
	gboolean status;
	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;
	GtkWidget *toplevel;

	if((te == NULL) || FileBrowserIsQuery())
	    return;

	toplevel = te->toplevel;

	/* Prompt the user to save the text as needed */
	DO_CHECK_CHANGES_AND_QUERY_USER(te);

	TEditSetBusy(te, TRUE);

	/* Create the file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    ".txt", "Text files"
	);

	/* Query the user for the file to open */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Open File",
	    "Open", "Cancel",
	    NULL,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* Got user response? */
	if(status)
	{
	    gchar *path = (total_path_rtns > 0) ?
		STRDUP(path_rtn[0]) : NULL;

	    /* Open the file */
	    TEditOpenFile(te, path, TRUE);

	    /* Set the new file name */
	    if(path != te->filename)
	    {
		g_free(te->filename);
		te->filename = STRDUP(path);
	    }

	    te->modified = FALSE;

	    g_free(path);
	}

	/* Delete the file types list */
	FileBrowserDeleteTypeList(ftype, total_ftypes);

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Open file.
 *
 *	The te specifies the Text Editor.
 *
 *	The path specifies the full path to the text file to open.
 *
 *	If verbose is TRUE then any warning and error messages will
 *	be displayed.
 */
static void TEditOpenFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
)
{
	FILE *fp;
	struct stat stat_buf;
	gchar *read_buf;
	gulong read_buf_len;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	/* Open the file for reading */
	fp = fopen(path, "rb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Open Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    return;
	}

	/* Get the file's statistics */
	if(fstat(fileno(fp), &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    fclose(fp);
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Open Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    return;
	}

	gtk_text_freeze(text);

	/* Clear the GtkText's text */
	gtk_editable_delete_text(editable, 0, -1);
	gtk_text_set_point(text, 0);

	/* Allocate the read buffer */
	read_buf_len = (gulong)stat_buf.st_blksize;
	if(read_buf_len > 0)
	    read_buf = (gchar *)g_malloc(read_buf_len * sizeof(gchar));
	else
	    read_buf = NULL;
	if(read_buf != NULL)
	{
	    gint units_read;

	    /* Begin reading the file */
	    clearerr(fp);
	    while(!feof(fp))
	    {
		/* Read this block */
		units_read = (gint)fread(
		    read_buf, sizeof(guint8), (size_t)read_buf_len, fp
		);
		if(units_read <= 0)
		{
		    const gint error_code = (gint)errno;
		    if(units_read < 0)
		    {
			if(verbose)
			{
			    gchar *error_msg = STRDUP(g_strerror(error_code));
			    if(error_msg != NULL)
			    {
				gchar *s = g_strdup_printf(
				    "%s:\n\n    %s",
				    error_msg, path
				);
				EDVPlaySoundError(te->ctx);
				CDialogSetTransientFor(toplevel);
				CDialogGetResponse(
				    "Insert Failed",
				    s,
				    NULL,
				    CDIALOG_ICON_ERROR,
				    CDIALOG_BTNFLAG_OK,
				    CDIALOG_BTNFLAG_OK
				);
				CDialogSetTransientFor(NULL);
				g_free(s);
				g_free(error_msg);
			    }
			}
		    }
		    break;
		}

		/* Append this block to the GtkText */
		gtk_text_insert(
		    text,
		    NULL,
		    NULL, NULL,
		    read_buf, units_read
		);
	    }

	    /* Delete the read buffer */
	    g_free(read_buf);
	}
	else
	{
	    /* No read buffer, open one character at a time */
	    gint c;
	    gchar buf[1];

	    /* Begin reading the file */
	    clearerr(fp);
	    while(!feof(fp))
	    {
		c = (gint)fgetc(fp);
		if(c == EOF)
		    break;

		buf[0] = (gchar)c;

		/* Append this character to the GtkText */
		gtk_text_insert(
		    text,
		    NULL,
		    NULL, NULL,
		    buf, 1
		);
	    }
	}    

	gtk_text_thaw(text);

	/* Close the file */
	fclose(fp);
}

/*
 *	Insert file query.
 *
 *	Prompts the user for a file to insert.
 */
static void TEditInsertFileQuery(tedit_struct *te)
{
	gboolean status;
	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;
	GtkWidget *toplevel;

	if((te == NULL) || FileBrowserIsQuery())
	    return;

	toplevel = te->toplevel;

	TEditSetBusy(te, TRUE);

	/* Create the file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    ".txt", "Text files"
	);

	/* Query the user for the file to insert */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Insert File",
	    "Insert", "Cancel",
	    NULL,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* Got user response? */
	if(status)
	{
	    gchar *path = (total_path_rtns > 0) ?
		STRDUP(path_rtn[0]) : NULL;

	    /* Insert the file */
	    TEditInsertFile(te, path, TRUE);

	    if(!te->modified)
		te->modified = TRUE;

	    g_free(path);
	}

	/* Delete the file types list */
	FileBrowserDeleteTypeList(ftype, total_ftypes);

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Insert file.
 *
 *	The te specifies the Text Editor.
 *
 *	The path specifies the full path to the text file to insert.
 *
 *	If verbose is TRUE then any warning and error messages will
 *	be displayed.
 */
static void TEditInsertFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
)
{
	FILE *fp;
	struct stat stat_buf;
	gint insert_pos, total_units_read;
	gchar *read_buf;
	gulong read_buf_len;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	/* Open the file for reading */
	fp = fopen(path, "rb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    gchar *error_msg = STRDUP(g_strerror(error_code));
	    if(error_msg != NULL)
	    {
		gchar *s = g_strdup_printf(
		    "%s:\n\n    %s",
		    error_msg, path
		);
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Insert Failed",
		    s,
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(s);
		g_free(error_msg);
	    }
	    return;
	}

	/* Get the file's statistics */
	if(fstat(fileno(fp), &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    gchar *error_msg = STRDUP(g_strerror(error_code));
	    if(error_msg != NULL)
	    {
		gchar *s = g_strdup_printf(
		    "%s:\n\n    %s",
		    error_msg, path
		);
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Insert Failed",
		    s,
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
		g_free(s);
		g_free(error_msg);
	    }
	    fclose(fp);
	    return;
	}

	gtk_text_freeze(text);

	/* Insert the text from the file at the GtkText's current
	 * position
	 */
	insert_pos = editable->current_pos;
	gtk_text_set_point(text, insert_pos);

	total_units_read = 0;

	/* Allocate the read buffer */
	read_buf_len = (gulong)stat_buf.st_blksize;
	if(read_buf_len > 0)
	    read_buf = (gchar *)g_malloc(read_buf_len * sizeof(gchar));
	else
	    read_buf = NULL;
	if(read_buf != NULL)
	{
	    gint units_read;

	    /* Begin reading the file */
	    clearerr(fp);
	    while(!feof(fp))
	    {
		/* Read this block */
		units_read = (gint)fread(
		    read_buf, sizeof(guint8), (size_t)read_buf_len, fp
		);
		if(units_read <= 0)
		{
		    const gint error_code = (gint)errno;
		    if(units_read < 0)
		    {
			if(verbose)
			{
			    gchar *error_msg = STRDUP(g_strerror(error_code));
			    if(error_msg != NULL)
			    {
				gchar *s = g_strdup_printf(
				    "%s:\n\n    %s",
				    error_msg, path
				);
				EDVPlaySoundError(te->ctx);
				CDialogSetTransientFor(toplevel);
				CDialogGetResponse(
				    "Insert Failed",
				    s,
				    NULL,
				    CDIALOG_ICON_ERROR,
				    CDIALOG_BTNFLAG_OK,
				    CDIALOG_BTNFLAG_OK
				);
				CDialogSetTransientFor(NULL);
				g_free(s);
				g_free(error_msg);
			    }
			}
		    }
		    break;
		}
		total_units_read += units_read;

		/* Append this block to the GtkText */
		gtk_text_insert(
		    text,
		    NULL,
		    NULL, NULL,
		    read_buf, units_read
		);
	    }

	    /* Delete the read buffer */
	    g_free(read_buf);
	}
	else
	{
	    /* No read buffer, open one character at a time */
	    gint c;
	    gchar buf[1];

	    /* Begin reading the file */
	    clearerr(fp);
	    while(!feof(fp))
	    {
		c = (gint)fgetc(fp);
		if(c == EOF)
		    break;

		buf[0] = (gchar)c;

		total_units_read++;

		/* Append this character to the GtkText */
		gtk_text_insert(
		    text,
		    NULL,
		    NULL, NULL,
		    buf, 1
		);
	    }
	}    

	gtk_text_thaw(text);

	/* Close the file */
	fclose(fp);

	/* Need to flush events after inserting the file and before
	 * doing anything else or else the GtkText crashes
	 */
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();

	gtk_text_freeze(text);

	/* Position the cursor after the inserted text */
	gtk_editable_set_position(
	    editable,
	    insert_pos + total_units_read
	);
	gtk_text_thaw(text);
}

/*
 *	Save.
 *
 *	Saves the text to the current file and prompts the user as
 *	needed if the text does not have an associated file name.
 */
static void TEditSave(tedit_struct *te)
{
	GtkWidget *toplevel;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;

	/* If there is no filename associated with the text then call
	 * TEditSaveAs()
	 */
	if(te->filename == NULL)
	{
	    TEditSaveAs(te);
	    return;
	}

	TEditSetBusy(te, TRUE);

	/* Save the file */
	if(TEditSaveFile(te, te->filename, TRUE) == 0)
	{
	    /* File saved successfully */
	    te->modified = FALSE;
	}

	/* Notify about the file being modified */
	EDVNotifyQueueObjectModified(
	    te->ctx, te->filename, NULL
	);
	EDVContextSync(te->ctx);

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Save as.
 *
 *	Prompts the user to save the text to a file.
 */
static void TEditSaveAs(tedit_struct *te)
{
	gboolean status;
	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;
	GtkWidget *toplevel;

	if((te == NULL) || FileBrowserIsQuery())
	    return;

	toplevel = te->toplevel;

	TEditSetBusy(te, TRUE);

	/* Create the file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    ".txt", "Text files"
	);
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);

	/* Query the user for the file to save */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Save File As",
	    "Save", "Cancel",
	    NULL,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* Got user response? */
	if(status)
	{
	    gboolean file_exists = FALSE;
	    gchar *path = (total_path_rtns > 0) ?
		STRDUP(path_rtn[0]) : NULL;
	    if(path != NULL)
	    {
		gboolean allow_save = TRUE;

		/* Check if the file already exists */
		file_exists = access(path, F_OK) ? FALSE : TRUE;
		if(file_exists)
		{
		    gint response;
		    gchar *s = g_strdup_printf(
"Overwrite existing file:\n\n    %s",
			path
		    );
		    EDVPlaySoundWarning(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    response = CDialogGetResponse(
			"Confirm Overwrite",
			s,
			NULL,
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    switch(response)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
		      case CDIALOG_RESPONSE_YES:
			break;

		      default:
			allow_save = FALSE;
			break;
		    }
		}

		/* File does not exist or user permits ovewrite? */
		if(allow_save)
		{
		    /* Save the file */
		    if(TEditSaveFile(te, path, TRUE) == 0)
		    {
			/* File saved successfully */

			/* Update the file name */
			if(path != te->filename)
			{
			    g_free(te->filename);
			    te->filename = STRDUP(path);
			}

			te->modified = FALSE;
		    }

		    /* Notify about the file being modified or added */
		    if(file_exists)
			EDVNotifyQueueObjectModified(
			    te->ctx, te->filename, NULL
			);
		    else
			EDVNotifyQueueObjectAdded(
			    te->ctx, te->filename
			);
		    EDVContextSync(te->ctx);
		}
		g_free(path);
	    }
	}	/* Got user response? */

	/* Delete file types list */
	FileBrowserDeleteTypeList(ftype, total_ftypes);

	/* Reset due to possible file related change */
	FileBrowserReset();

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Save file.
 *
 *	The te specifies the Text Editor.
 *
 *	The path specifies the full path to the file to save to. The
 *	existance of the file is not checked so any checking for
 *	overwrites must be done prior to this call.
 *
 *	If verbose is TRUE then any warning or error messages will
 *	be displayed.
 */
static gint TEditSaveFile(
	tedit_struct *te,
	const gchar *path,
	const gboolean verbose
)
{
	FILE *fp;
	struct stat stat_buf;
	gint status, len, cur_len, units_written;
	gchar *buf, *buf_ptr, *buf_end;
	gulong write_buf_len;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if((te == NULL) || STRISEMPTY(path))
	    return(-2);

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	/* Open the file for writing */
	fp = fopen(path, "wb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Save Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    return(-1);
	}

	/* Get the file's statistics */
	if(fstat(fileno(fp), &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    fclose(fp);
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Save Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    return(-1);
	}

	/* Get the copy of the text from the GtkText */
	len = (gint)gtk_text_get_length(text);
	buf = gtk_editable_get_chars(editable, 0, len);
	if(buf == NULL)
	{
	    fclose(fp);
	    if(verbose)
	    {
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Save Failed",
		    "Unable to obtain the text from the GtkText",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    return(-1);
	}

	/* Get the write buffer size */
	write_buf_len = MAX(
	    (gulong)stat_buf.st_blksize,
	    1l
	);

	/* Begin writing the file */
	status = 0;
	buf_ptr = buf;
	buf_end = buf + len;
	while(buf_ptr < buf_end)
	{
	    cur_len = MIN(
		(gint)(buf_end - buf_ptr),
		(gint)write_buf_len
	    );
	    if(cur_len <= 0)
		break;

	    /* Write this block */
	    units_written = (gint)fwrite(
		buf_ptr, sizeof(gchar), (size_t)cur_len, fp
	    );
	    if(units_written <= 0)
	    {
		const gint error_code = (gint)errno;
		if((units_written < 0) && verbose)
		{
		    gchar *error_msg = STRDUP(g_strerror(error_code));
		    if(error_msg != NULL)
		    {
			gchar *s = g_strdup_printf(
			    "%s:\n\n    %s",
			    error_msg, path
			);
			EDVPlaySoundError(te->ctx);
			CDialogSetTransientFor(toplevel);
			CDialogGetResponse(
			    "Save Failed",
			    s,
			    NULL,
			    CDIALOG_ICON_ERROR,
			    CDIALOG_BTNFLAG_OK,
			    CDIALOG_BTNFLAG_OK
			);
			CDialogSetTransientFor(NULL);
			g_free(s);
			g_free(error_msg);
		    }
		}
		status = -1;
		break;
	    }

	    buf_ptr += units_written;
	}

	/* Close the file and delete the copy of the text */
	fclose(fp);
	g_free(buf);

	return(status);
}


/*
 *	Queries the user to print the text.
 */
static void TEditPrintQuery(tedit_struct *te)
{
	gchar **strv;
	gint strc;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if((te == NULL) || PDialogIsQuery())
	    return;

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	TEditSetBusy(te, TRUE);

	PDialogDeleteAllPrompts();
 	PDialogAddPrompt(NULL, "Command:", te->last_print_cmd);
	PDialogAddPromptSpin(
	    NULL, "Number Of Coppies:",
	    1.0f, 0.0f, 1000000.0f,
	    1.0f, 5.0f, 1.0f, 0
	);
	PDialogSetSize(320, -1);
	PDialogSetTransientFor(toplevel);
	strv = PDialogGetResponse(
	    "Print", NULL, NULL,
	    PDIALOG_ICON_PRINTER,
	    "Print", "Cancel",
	    PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
	    PDIALOG_BTNFLAG_SUBMIT,
	    &strc
	);
	PDialogSetTransientFor(NULL);

	if((strv != NULL) && (strc > 0))
	{
	    /* Record the last print command */
	    if(strc > 0)
	    {
		g_free(te->last_print_cmd);
		te->last_print_cmd = STRDUP(strv[0]);
	    }

	    /* Print */
	    TEditPrint(
		te,
		te->last_print_cmd,
		(strc > 1) ? ATOI(strv[1]) : 1,
		TRUE		/* Verbose */
	    );
	}

	TEditUpdate(te);
	TEditSetBusy(te, FALSE);
}

/*
 *	Prints the text.
 */
static gint TEditPrint(
	tedit_struct *te,
	const gchar *print_cmd,
	const gint ncoppies,
	const gboolean verbose
)
{
	struct stat stat_buf;
	FILE *fp;
	gint status, fd, len, cur_len, units_written;
	gulong write_buf_len;
	const gchar *dir;
	gchar *path, *buf, *buf_ptr, *buf_end;
	GtkWidget *toplevel;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return(-2);

	toplevel = te->toplevel;
	text = GTK_TEXT(te->text);
	editable = GTK_EDITABLE(text);

	if(STRISEMPTY(print_cmd))
	    print_cmd = TEDIT_DEF_PRINT_SPOOLER;

	/* Get the text from the GtkText to be printed */
        len = (gint)gtk_text_get_length(text);
        buf = gtk_editable_get_chars(editable, 0, len);
        if(buf == NULL)
	{
	    if(verbose)
	    {
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Print Failed",
		    "Unable to obtain the text from the GtkText",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
            return(-1);
	}

	/* Generate a tempory file name */
	dir = g_getenv(ENV_VAR_NAME_TMPDIR);
        if(STRISEMPTY(dir))
#if defined(P_tmpdir)
            dir = P_tmpdir;
#elif defined(_WIN32)
            dir = "C:\\TEMP";
#else
            dir = "/tmp";
#endif
	path = g_strdup_printf(
	    "%s%c%s",
	    dir, G_DIR_SEPARATOR, PROG_NAME "XXXXXX"
	);
	if(path == NULL)
	{
	    if(verbose)
	    {
		EDVPlaySoundError(te->ctx);
		CDialogSetTransientFor(toplevel);
		CDialogGetResponse(
		    "Print Failed",
		    "Unable to generate a tempory file path",
		    NULL,
		    CDIALOG_ICON_ERROR,
		    CDIALOG_BTNFLAG_OK,
		    CDIALOG_BTNFLAG_OK
		);
		CDialogSetTransientFor(NULL);
	    }
	    g_free(buf);
	    return(-1);
	}
        fd = (gint)mkstemp((char *)path);
        if(fd > -1)                      
            close((int)fd);

	/* Open the file for writing */
	fp = fopen(path, "wb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Print Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    g_free(buf);
	    unlink(path);
	    g_free(path);
	    return(-1);
	}

	/* Get the file's statistics */
	if(fstat(fileno(fp), &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    if(verbose)
	    {
		gchar *error_msg = STRDUP(g_strerror(error_code));
		if(error_msg != NULL)
		{
		    gchar *s = g_strdup_printf(
			"%s:\n\n    %s",
			error_msg, path
		    );
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Print Failed",
			s,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(error_msg);
		}
	    }
	    g_free(buf);
	    fclose(fp);
	    unlink(path);
	    g_free(path);
	    return(-1);
	}

	/* Get the write buffer size */
	write_buf_len = MAX(
	    (gulong)stat_buf.st_blksize,
	    1l
	);

	/* Begin writing the file */
	status = 0;
	buf_ptr = buf;
	buf_end = buf + len;
	while(buf_ptr < buf_end)
	{
	    cur_len = MIN(
		(gint)(buf_end - buf_ptr),
		(gint)write_buf_len
	    );
	    if(cur_len <= 0)
		break;

	    /* Write this block */
	    units_written = (gint)fwrite(
		buf_ptr, sizeof(gchar), (size_t)cur_len, fp
	    );
	    if(units_written <= 0)
	    {
		const gint error_code = (gint)errno;
		if((units_written < 0) && verbose)
		{
		    gchar *error_msg = STRDUP(g_strerror(error_code));
		    EDVPlaySoundError(te->ctx);
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
			"Print Failed",
			error_msg,
			NULL,
			CDIALOG_ICON_ERROR,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(error_msg);
		}
		status = -1;
		break;
	    }

	    buf_ptr += units_written;
	}

	/* Delete the buffer */
	g_free(buf);

	/* Close the file */
	fclose(fp);

	/* Did an error occure while writing the file? */
	if(status != 0)
	{
	    unlink(path);
	    g_free(path);
	    return(-1);
	}

	if((path != NULL) && (print_cmd != NULL) && (ncoppies > 0))
	{
	    gint p;
	    gchar *cmd;

	    /* Format the print command */
	    if(ncoppies > 1)
		cmd = g_strdup_printf(
		    "%s -#%i \"%s\"",
		    print_cmd, ncoppies, path
		);
	    else
		cmd = g_strconcat(
		    print_cmd, " \"", path, "\"", NULL
		);

	    /* Execute the print command */
	    p = (gint)Exec((char *)cmd);

	    /* Delete the print command */
	    g_free(cmd);

	    /* Map the progress dialog and monitor the printing
	     * progress
	     */
	    ProgressDialogSetTransientFor(toplevel);
	    ProgressDialogMap(
		"Printing",
		"Printing...",
		(const guint8 **)icon_print_32x32_xpm,
		"Stop"
	    );
            while(ExecProcessExists(p))
	    {
		ProgressDialogUpdateUnknown(
		    NULL, NULL, NULL, NULL, TRUE 
		);
		if(ProgressDialogStopCount() > 0)
		{
		    kill(p, SIGINT);
		    break;
		}
	    }
            ProgressDialogBreakQuery(TRUE);
            ProgressDialogSetTransientFor(NULL);
	}

	/* Delete the tempory file and its path */
	unlink(path);
	g_free(path);

	return(0);
}


/*
 *	Close.
 */
static void TEditClose(tedit_struct *te)
{
	GtkWidget *toplevel;

	if(te == NULL)
	    return;

	toplevel = te->toplevel;

	/* Prompt the user to save the text as needed */
	DO_CHECK_CHANGES_AND_QUERY_USER(te);

	/* Since we only have one window we can safely and simply
	 * pop the main loop here
	 */
	gtk_main_quit();
}


/*
 *	Builds the menu bar.
 */
static void TEditBuildMenuBar(tedit_struct *te, GtkWidget *parent)
{
	guint8 **icon;
	const gchar *label;
	guint accel_key, accel_mods;
	GtkAccelGroup *accelgrp;
	GtkWidget *menu_bar, *menu, *w;
	gpointer mclient_data = te;
	void (*func_cb)(GtkWidget *w, gpointer);

	if((te == NULL) || (parent == NULL))
	    return;

	/* Get the keyboard accelerator group */
	accelgrp = te->accelgrp;

	/* Create the menu bar */
	menu_bar = GUIMenuBarCreate(NULL);
	if(GTK_IS_BOX(parent))
	    gtk_box_pack_start(GTK_BOX(parent), menu_bar, FALSE, FALSE, 0);
	else
	    gtk_container_add(GTK_CONTAINER(parent), menu_bar);
	gtk_widget_show(menu_bar);

#define DO_ADD_MENU_ITEM_LABEL	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_ITEM_CHECK	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_SEP		{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}

	/* Create each menu */

	/* File menu */
	menu = GUIMenuCreateTearOff();
	if(menu != NULL)
	{
	    icon = (guint8 **)icon_new_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Nuevo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nouveau"
#elif defined(PROG_LANGUAGE_GERMAN)
"Neu"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Nuovo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Nieuw"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Novo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ny"
#else
"New"
#endif
	    ;
	    accel_key = GDK_n;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditNewTextCB;
	    DO_ADD_MENU_ITEM_LABEL

	    icon = (guint8 **)icon_open_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Abierto..."
#elif defined(PROG_LANGUAGE_FRENCH)
"Ouvrir..."
#elif defined(PROG_LANGUAGE_GERMAN)
"Offen..."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Aperto..."
#elif defined(PROG_LANGUAGE_DUTCH)
"Open..."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Aberto..."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpen..."
#else
"Open..."
#endif
	    ;
	    accel_key = GDK_o;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditOpenCB;
	    DO_ADD_MENU_ITEM_LABEL

	    icon = (guint8 **)icon_save_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Salve"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enregister"
#elif defined(PROG_LANGUAGE_GERMAN)
"Sparen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Risparmiare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Red"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Poupe"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Untatt"
#else
"Save"
#endif
	    ;
	    accel_key = GDK_s;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditSaveCB;
	    DO_ADD_MENU_ITEM_LABEL

	    icon = (guint8 **)icon_save_as_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Salve Como..."
#elif defined(PROG_LANGUAGE_FRENCH)
"Enregister sous..."
#elif defined(PROG_LANGUAGE_GERMAN)
"Außer Als..."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Risparmiare Come..."
#elif defined(PROG_LANGUAGE_DUTCH)
"Behalve Als..."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Poupe Como..."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Untatt As..."
#else
"Save As..."
#endif
	    ;
	    accel_key = GDK_a;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditSaveAsCB;
	    DO_ADD_MENU_ITEM_LABEL

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_print_20x20_xpm;
	    label = "Print...";
	    accel_key = GDK_p;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditPrintCB;
	    DO_ADD_MENU_ITEM_LABEL

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_close_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
		"Cierre"
#elif defined(PROG_LANGUAGE_FRENCH)
		"Proche"
#elif defined(PROG_LANGUAGE_POLISH)
		"Zamknij"
#else
		"Close"
#endif
	    ;
	    accel_key = GDK_w;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditCloseCB;
	    DO_ADD_MENU_ITEM_LABEL
	}
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
#if defined(PROG_LANGUAGE_SPANISH)
"Archivo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fichier"
#elif defined(PROG_LANGUAGE_GERMAN)
"Akte"
#elif defined(PROG_LANGUAGE_ITALIAN)
"File"
#elif defined(PROG_LANGUAGE_DUTCH)
"Dossier"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquivo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Arkiv"
#else
"File"
#endif
	    , GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Edit menu */
	menu = GUIMenuCreateTearOff();
	if(menu != NULL)
	{
	    icon = (guint8 **)icon_cut_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Corte"
#elif defined(PROG_LANGUAGE_FRENCH)
"Couper"
#elif defined(PROG_LANGUAGE_GERMAN)
"Schnitt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Taglio"
#elif defined(PROG_LANGUAGE_DUTCH)
"Snee"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Corte"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Snitt"
#else
"Cut"
#endif
	    ;
	    accel_key = GDK_x;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditCutCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->cut_mi = w;

	    icon = (guint8 **)icon_copy_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Copia"
#elif defined(PROG_LANGUAGE_FRENCH)
"Copier"
#elif defined(PROG_LANGUAGE_GERMAN)
"Kopie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"La Copia"
#elif defined(PROG_LANGUAGE_DUTCH)
"Kopie"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"A Cópia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Kopi"
#else
"Copy"
#endif
	    ;
	    accel_key = GDK_c;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditCopyCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->copy_mi = w;

	    icon = (guint8 **)icon_paste_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Pasta"
#elif defined(PROG_LANGUAGE_FRENCH)
"Coller"
#elif defined(PROG_LANGUAGE_GERMAN)
"Paste"
#elif defined(PROG_LANGUAGE_ITALIAN)
"La Pasta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Plakmiddel"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"A Pasta"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Masse"
#else
"Paste"
#endif
	    ;
	    accel_key = GDK_v;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditPasteCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->paste_mi = w;

	    icon = (guint8 **)icon_cancel_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Couper"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk"
#else
"Delete"
#endif
	    ;
	    accel_key = GDK_Delete;
	    accel_mods = 0;
	    func_cb = TEditDeleteCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->delete_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Select All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TEditSelectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->select_all_mi = w;

	    icon = NULL;
	    label = "Unselect All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TEditUnselectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->unselect_all_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_search_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Hallazgo..."
#elif defined(PROG_LANGUAGE_FRENCH)
"Rechercher..."
#elif defined(PROG_LANGUAGE_GERMAN)
"Fund..."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Trovare..."
#elif defined(PROG_LANGUAGE_DUTCH)
"Vondst..."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ache..."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Funn..."
#else
"Find..."
#endif
	    ;
	    accel_key = GDK_f;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditFindCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->find_mi = w;

	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Encuentre Luego"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rechercher suivant"
#elif defined(PROG_LANGUAGE_GERMAN)
"Finden Sie Nächst"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Trovare Vicino"
#elif defined(PROG_LANGUAGE_DUTCH)
"Vind Volgend"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ache Logo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Finn Next"
#else
"Find Next"
#endif
	    ;
	    accel_key = GDK_F6;
	    accel_mods = 0;
	    func_cb = TEditFindNextCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->find_next_mi = w;

	    icon = (guint8 **)icon_replace_20x20_xpm;
	    label = "Replace...";
	    accel_key = GDK_r;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditReplaceCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->replace_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_add_20x20_xpm;
	    label = "Insert File...";
	    accel_key = GDK_i;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditInsertFileCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->insert_file_mi = w;
	}
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
#if defined(PROG_LANGUAGE_SPANISH)
"Redacte"
#elif defined(PROG_LANGUAGE_FRENCH)
"Editer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Redigieren"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Redigere"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bewerking"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Edite"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Rediger"
#else
"Edit"
#endif
	    , GUI_MENU_BAR_ALIGN_LEFT
	);


	/* Help menu */
	menu = GUIMenuCreateTearOff();
	if(menu != NULL)
	{
	    icon = (guint8 **)icon_about_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Acerca De"
#elif defined(PROG_LANGUAGE_FRENCH)
"A propos"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ungefähr"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Circa"
#elif defined(PROG_LANGUAGE_DUTCH)
"Over"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Sobre"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Omtrent"
#else
"About"
#endif
	    ;
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TEditAboutCB;
	    DO_ADD_MENU_ITEM_LABEL
	}
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
#if defined(PROG_LANGUAGE_SPANISH)
"Ayuda"
#elif defined(PROG_LANGUAGE_FRENCH)
"Aide"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hilfe"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aiuto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Hulp"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ajuda"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Hjelp"
#else
"Help"
#endif
	    , GUI_MENU_BAR_ALIGN_RIGHT
	);


#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP
}

/*
 *	Builds the popup menu.
 */
static void TEditBuildPopupMenu(tedit_struct *te)
{
	guint8 **icon;
	const gchar *label;
	guint accel_key, accel_mods;
	GtkAccelGroup *accelgrp = NULL;
	GtkWidget *menu, *w;
	gpointer mclient_data = te;
	void (*func_cb)(GtkWidget *w, gpointer);

	te->popup_menu = menu = GUIMenuCreate();

#define DO_ADD_MENU_ITEM_LABEL	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_ITEM_CHECK	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_SEP		{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}

	if(menu != NULL)
	{
	    icon = (guint8 **)icon_cut_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Corte"
#elif defined(PROG_LANGUAGE_FRENCH)
"Couper"
#elif defined(PROG_LANGUAGE_GERMAN)
"Schnitt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Taglio"
#elif defined(PROG_LANGUAGE_DUTCH)
"Snee"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Corte"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Snitt"
#else
"Cut"
#endif
	    ;
	    accel_key = GDK_x;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditCutCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_cut_mi = w;

	    icon = (guint8 **)icon_copy_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Copia"
#elif defined(PROG_LANGUAGE_FRENCH)
"Copier"
#elif defined(PROG_LANGUAGE_GERMAN)
"Kopie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"La Copia"
#elif defined(PROG_LANGUAGE_DUTCH)
"Kopie"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"A Cópia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Kopi"
#else
"Copy"
#endif
	    ;
	    accel_key = GDK_c;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditCopyCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_copy_mi = w;

	    icon = (guint8 **)icon_paste_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Pasta"
#elif defined(PROG_LANGUAGE_FRENCH)
"Coller"
#elif defined(PROG_LANGUAGE_GERMAN)
"Paste"
#elif defined(PROG_LANGUAGE_ITALIAN)
"La Pasta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Plakmiddel"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"A Pasta"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Masse"
#else
"Paste"
#endif
	    ;
	    accel_key = GDK_v;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditPasteCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_paste_mi = w;

	    icon = (guint8 **)icon_cancel_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Couper"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk"
#else
"Delete"
#endif
	    ;
	    accel_key = GDK_Delete;
	    accel_mods = 0;
	    func_cb = TEditDeleteCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_delete_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Select All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TEditSelectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_select_all_mi = w;

	    icon = NULL;
	    label = "Unselect All";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TEditUnselectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_unselect_all_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_search_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Hallazgo..."
#elif defined(PROG_LANGUAGE_FRENCH)
"Rechercher..."
#elif defined(PROG_LANGUAGE_GERMAN)
"Fund..."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Trovare..."
#elif defined(PROG_LANGUAGE_DUTCH)
"Vondst..."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ache..."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Funn..."
#else
"Find..."
#endif
	    ;
	    accel_key = GDK_f;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditFindCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_find_mi = w;

	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Encuentre Luego"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rechercher suivant"
#elif defined(PROG_LANGUAGE_GERMAN)
"Finden Sie Nächst"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Trovare Vicino"
#elif defined(PROG_LANGUAGE_DUTCH)
"Vind Volgend"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ache Logo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Finn Next"
#else
"Find Next"
#endif
	    ;
	    accel_key = GDK_F6;
	    accel_mods = 0;
	    func_cb = TEditFindNextCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_find_next_mi = w;

	    icon = (guint8 **)icon_replace_20x20_xpm;
	    label = "Replace...";
	    accel_key = GDK_r;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditReplaceCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_replace_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_add_20x20_xpm;
	    label = "Insert File...";
	    accel_key = GDK_i;
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = TEditInsertFileCB;
	    DO_ADD_MENU_ITEM_LABEL
	    te->popup_insert_file_mi = w;
	}

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP
}

/*
 *	Creates a new Text Editor.
 */
static tedit_struct *TEditNew(
	const gint argc, gchar **argv,
	edv_context_struct *ctx
)
{
	const gint border_minor = 2;
	gchar *s;
	GdkWindow *window;
	GtkRcStyle *rcstyle;
	GtkWidget *w, *parent, *parent2, *sb;
	GtkEditable *editable;
	GtkText *text;
	tedit_struct *te = TEDIT(g_malloc0(sizeof(tedit_struct)));
	if(te == NULL)
	    return(NULL);

	te->accelgrp = gtk_accel_group_new();
	te->busy_count = 0;
	te->freeze_count = 0;
	te->busy_cur = gdk_cursor_new(GDK_WATCH);
	te->text_cur = gdk_cursor_new(GDK_XTERM);
	te->ctx = ctx;

	te->filename = NULL;

	s = g_getenv(ENV_VAR_NAME_PRINTCOMMAND);
	if(s == NULL)
	    s = g_getenv(ENV_VAR_NAME_PRINTCMD);
	if(s == NULL)
	    s = g_getenv(ENV_VAR_NAME_PRINT);
	if(s != NULL)
	{
	    te->last_print_cmd = STRDUP(s);
	}
	else
	{
	    /* Format the default print command */
	    const gchar *printer = g_getenv(ENV_VAR_NAME_PRINTER);
	    te->last_print_cmd = g_strconcat(
		TEDIT_DEF_PRINT_SPOOLER,
		(printer != NULL) ? " -P" : "",
		(printer != NULL) ? printer : "",
		NULL
	    );
	}
	te->last_find_str = NULL;
	te->last_replace_str = NULL;
	te->last_find_case_sensitive = FALSE;

	/* Toplevel */
	te->toplevel = w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_widget_set_usize(w, TEDIT_WIDTH, TEDIT_HEIGHT);
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "texteditor", PROG_NAME
	);
	gtk_window_set_policy(
	    GTK_WINDOW(w),
	    TRUE, TRUE, TRUE
	);
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    GdkGeometry geo;

	    geo.min_width = 100;
	    geo.min_height = 70;

	    geo.base_width = 0;
	    geo.base_height = 0;

	    geo.width_inc = 1;
	    geo.height_inc = 1;

	    gdk_window_set_geometry_hints(
		window, &geo,
		GDK_HINT_MIN_SIZE |
		GDK_HINT_BASE_SIZE |
		GDK_HINT_RESIZE_INC
	    );

	    gdk_window_set_decorations(
		window,
		GDK_DECOR_BORDER | GDK_DECOR_TITLE | GDK_DECOR_MENU |
		GDK_DECOR_RESIZEH | GDK_DECOR_MINIMIZE |
	        GDK_DECOR_MAXIMIZE
	    );
	    gdk_window_set_functions(
		window,
		GDK_FUNC_MOVE | GDK_FUNC_RESIZE |
		GDK_FUNC_MINIMIZE | GDK_FUNC_MAXIMIZE |
		GDK_FUNC_CLOSE
	    );

	    GUISetWMIcon(window, (guint8 **)icon_edit_48x48_xpm);
	}
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(TEditDeleteEventCB), te
	);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_window_add_accel_group(GTK_WINDOW(w), te->accelgrp);
	gtk_window_apply_args(GTK_WINDOW(w), argc, argv);
	parent = w;

	te->main_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	te->menu_bar_handle = w = gtk_handle_box_new();
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	TEditBuildMenuBar(te, parent2);


	/* Text */
	w = gtk_table_new(2, 2, FALSE);
	gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	rcstyle = gtk_rc_style_new(); 
	rcstyle->font_name = g_strdup(
#if defined(PROG_LANGUAGE_POLISH)
"-adobe-courier-medium-r-normal-*-12-*-*-*-*-*-iso8859-2"
#else
"-adobe-courier-medium-r-normal-*-12-*-*-*-*-*-iso8859-1"
#endif   
	);
	te->text = w = gtk_text_new(NULL, NULL);
	editable = GTK_EDITABLE(w);
	text = GTK_TEXT(w);
	text->default_tab_width = 8;
	gtk_text_set_editable(text, TRUE);
	gtk_text_set_word_wrap(text, TRUE);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 0, 1,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(TEditTextEventCB), te
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(TEditTextEventCB), te
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(TEditTextEventCB), te
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(TEditTextEventCB), te
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "changed",
	    GTK_SIGNAL_FUNC(TEditTextChangedCB), te
	);
	if(w != NULL)
	{
	    const GtkTargetEntry dnd_tar_types[] = {
{"text/plain",		0,		TEDIT_DND_TYPE_INFO_TEXT_PLAIN},
{"text/uri-list",	0,		TEDIT_DND_TYPE_INFO_TEXT_URI_LIST},
{"STRING",		0,		TEDIT_DND_TYPE_INFO_STRING}
	    };
	    GUIDNDSetTar(
		w,
		dnd_tar_types,
		sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
		GDK_ACTION_DEFAULT |            /* Drag actions */
		GDK_ACTION_LINK |
		GDK_ACTION_MOVE |
		GDK_ACTION_COPY,
		GDK_ACTION_MOVE,		/* Def act if same */
		GDK_ACTION_COPY,		/* Def act */
		TEditDNDDragReceivedCB,
		te
	    );
	}
	gtk_widget_realize(w);
	gtk_widget_modify_style(w, rcstyle);
	gdk_window_set_cursor(text->text_area, te->text_cur);
	gtk_widget_show(w);
	GTK_RC_STYLE_UNREF(rcstyle);
	te->hadj = text->hadj;
	te->vadj = text->vadj;
	/* Vertical GtkScrollBar */
	sb = gtk_vscrollbar_new(text->vadj);
	gtk_table_attach(
	    GTK_TABLE(parent2), sb,
	    1, 2, 0, 1,
	    GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(sb);


	/* Build the popup menu */
	TEditBuildPopupMenu(te);


	return(te);
}

/*
 *	Sets the Text Editor as busy or ready.
 */
static void TEditSetBusy(tedit_struct *te, const gboolean busy)
{
	GdkCursor *cur, *text_cur;
	GtkWidget *w;
	GtkText *text;

	if(te == NULL)
	    return;

	if(busy)
	{
	    te->busy_count++;
	    if(te->busy_count > 1)
		return;
	    text_cur = cur = te->busy_cur;
	}
	else
	{   
	    te->busy_count--;
	    if(te->busy_count < 0)
		te->busy_count = 0;
	    if(te->busy_count > 0)
		return;
	    cur = NULL;
	    text_cur = te->text_cur;
	}

	w = te->toplevel;
	if(w->window != NULL)
	    gdk_window_set_cursor(w->window, cur);

	w = te->text;
	text = GTK_TEXT(w);
	if(text->text_area != NULL)
	    gdk_window_set_cursor(text->text_area, text_cur);

	gdk_flush();
}

/*
 *	Updates Text Editor's widgets to reflect current values.
 */
static void TEditUpdate(tedit_struct *te)
{
	gboolean sensitive;
	GtkWidget *w;
	GtkEditable *editable;
	GtkText *text;

	if(te == NULL)
	    return;

	te->freeze_count++;

	text = GTK_TEXT(te->text);
	if(text == NULL)
	    return;

	editable = GTK_EDITABLE(text);

	w = te->toplevel;
	if(w != NULL)
	{
	    const gchar *s = (te->filename != NULL) ?
		strrchr(te->filename, G_DIR_SEPARATOR) : NULL;
	    gchar *title = g_strdup_printf(
		PROG_NAME_FULL ": %s%s",
		(s != NULL) ? s + 1 :
		    ((te->filename != NULL) ? te->filename : "Untitled"),
		te->modified ? " (*)" : ""
	    );
	    gtk_window_set_title(GTK_WINDOW(w), title);
	    g_free(title);
	}

	sensitive = (editable->selection_start_pos != 
	    editable->selection_end_pos) ? TRUE : FALSE;
	w = te->cut_mi;
	gtk_widget_set_sensitive(w, sensitive);
	w = te->popup_cut_mi;
	gtk_widget_set_sensitive(w, sensitive);
	w = te->copy_mi;
	gtk_widget_set_sensitive(w, sensitive);
	w = te->popup_copy_mi;
	gtk_widget_set_sensitive(w, sensitive);
	w = te->delete_mi;
	gtk_widget_set_sensitive(w, sensitive);
	w = te->popup_delete_mi;
	gtk_widget_set_sensitive(w, sensitive);

	sensitive = STRISEMPTY(te->last_find_str) ? FALSE : TRUE;
	w = te->find_next_mi;
        gtk_widget_set_sensitive(w, sensitive);
	w = te->popup_find_next_mi;
        gtk_widget_set_sensitive(w, sensitive);


	te->freeze_count--;
}

/*
 *	Deletes the Text Editor.
 */
static void TEditDelete(tedit_struct *te)
{
	if(te == NULL)
	    return;

	gtk_widget_destroy(te->popup_menu);
	gtk_widget_destroy(te->text);
	gtk_widget_destroy(te->menu_bar_handle);
	gtk_widget_destroy(te->toplevel);

	gtk_accel_group_unref(te->accelgrp);

	gdk_cursor_destroy(te->text_cur);
	gdk_cursor_destroy(te->busy_cur);

	g_free(te->filename);

	g_free(te->last_print_cmd);

	g_free(te->last_find_str);
	g_free(te->last_replace_str);

	g_free(te);
}


int main(int argc, char *argv[])
{
	gboolean initialized_gtk = FALSE;
	gint i;
	gint		line_num = 0;
	const gchar	*arg,
			*filename = NULL;
	tedit_struct *te;
	edv_context_struct *ctx;

#define DO_FREE_LOCALS	{		\
}

	/* Set up signal callbacks */
#ifdef SIGINT
	signal(SIGINT, TEditSignalCB);
#endif
#ifdef SIGTERM
	signal(SIGTERM, TEditSignalCB);
#endif
#ifdef SIGSEGV
	signal(SIGSEGV, TEditSignalCB);
#endif
#ifdef SIGSTOP
	signal(SIGSTOP, TEditSignalCB);
#endif
#ifdef SIGCONT
	signal(SIGCONT, TEditSignalCB);
#endif
#ifdef SIGPIPE
	signal(SIGPIPE, TEditSignalCB);
#endif

	/* Handle for arguments */
	for(i = 1; i < argc; i++)
	{
	    arg = argv[i];
	    if(arg == NULL)
		continue;

	    /* Help? */
	    if(!strcasecmp(arg, "--help") ||
	       !strcasecmp(arg, "-help") ||
	       !strcasecmp(arg, "--h") ||
	       !strcasecmp(arg, "-h") ||
	       !strcasecmp(arg, "-?")
	    )
	    {
		g_print(PROG_HELP_MESG);
		DO_FREE_LOCALS
		return(0);
	    }
	    /* Version? */
	    else if(!strcasecmp(arg, "--version") ||
		    !strcasecmp(arg, "-version")
	    )
	    {
		g_print(
		    "%s %s\n%s",
		    PROG_NAME_FULL, PROG_VERSION, PROG_COPYRIGHT
		);
		DO_FREE_LOCALS
		return(0);
	    }
	    /* Line Number? */
	    else if(!strcasecmp(arg, "--line") ||
		    !strcasecmp(arg, "-line") ||
		    !strcasecmp(arg, "--l") ||
		    !strcasecmp(arg, "-l")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		if(arg != NULL)
		{
		    line_num = ATOI(arg);
		}
		else
		{
		    g_printerr(
			"%s: Requires argument\n",
			argv[i - 1]
		    );
		    DO_FREE_LOCALS
		    return(2);
		}
	    }
	    /* Skip these arguments so that gtk_window_apply_args()
	     * handles them
	     */
	    else if(gtk_is_window_arg(arg))
	    {
		i++;
	    }
	    /* Single character argument? */
	    else if((*arg == '-') ? (arg[1] != '-') : FALSE)
	    {
		const gchar *v = arg + 1;
/*		gchar c; */

		while(*v != '\0')
		{
#if 0
		    c = *v;
		    if(c == 's')
		    {

		    }
		    else
		    {
			g_printerr(
"-%c: Unsupported argument.\n",
			    c
			);
			DO_FREE_LOCALS
			return(2);
		    }
#endif
		    v++;
		}
	    }
	    /* Non-option argument? */
	    else if((*arg != '-') && (*arg != '+'))
	    {
		filename = arg;
	    }
	    else
	    {
		g_printerr(
"%s: Unsupported argument.\n",
		    arg
		);
		DO_FREE_LOCALS
		return(2);
	    }
	}

	/* Set GTK+ locale */
	gtk_set_locale();

	/* Initialize GTK+ as needed */
	if(!initialized_gtk)
	{
	    if(!gtk_init_check(&argc, &argv))
	    {
		g_printerr("Unable to initialize GTK.\n");
		DO_FREE_LOCALS
		return(1);
	    }
	    initialized_gtk = TRUE;
	}

	/* Initialize GDK RGB buffers system */
	gdk_rgb_init();

	/* Initialize the dialogs */
	CDialogInit();
	ProgressDialogInit();
	FileBrowserInit();
	PDialogInit();

	/* Initialize the Endeavour 2 context */
	ctx = EDVContextNew();
	EDVContextLoadConfigurationFile(ctx, NULL);


	/* Create a new Text Editor */
	te = TEditNew(argc, argv, ctx);
	TEditUpdate(te);
	gtk_widget_show_raise(te->toplevel);
	gtk_widget_grab_focus(te->text);

	/* Open text file at startup? */
	if(filename != NULL)
	{
	    TEditSetBusy(te, TRUE);

	    /* Open the text file */
	    TEditOpenFile(te, filename, TRUE);

	    /* Set the new file name */
	    if(filename != te->filename)
	    {
		g_free(te->filename);
		te->filename = STRDUP(filename);
	    }

	    te->modified = FALSE;

	    /* Set the cursor position at startup? */
	    if((line_num > 1) || (line_num == -1))
	    {
		/* Need to flush events after opening the file and
		 * before setting the line position or else the
		 * GtkText crashes
		 */
		while(gtk_events_pending() > 0)
		    gtk_main_iteration();

		/* Scroll to the line */
		TEditScrollToLine(
		    te,
		    (line_num > 1) ? (line_num - 1) : -1
		);
	    }

	    TEditUpdate(te);

	    TEditSetBusy(te, FALSE);
	}


	gtk_main();


	/* Delete the Text Editor */
	TEditDelete(te);
	te = NULL;


	/* Delete the Endeavour context */
	EDVContextSync(ctx);
	EDVContextDelete(ctx);
	ctx = NULL;

	/* Shutdown dialogs */
	CDialogShutdown();
	ProgressDialogShutdown();
	FileBrowserShutdown();
	PDialogShutdown();

	/* Reset the DND Icon */
	GUIDNDSetDragIcon(NULL, NULL, 0, 0);

	DO_FREE_LOCALS

	return(0);
#undef DO_FREE_LOCALS
}
