#include <sys/stat.h>
#include <gtk/gtk.h>
#include "../include/fio.h"
#include "guiutils.h"
#include "tview.h"
#include "tviewcb.h"

#include "images/icon_cut_20x20.xpm"
#include "images/icon_copy_20x20.xpm"
#include "images/icon_paste_20x20.xpm"
#include "images/icon_cancel_20x20.xpm"
#include "images/icon_edit_48x48.xpm"


void TViewScrollToLine(
	tview_struct *tv, gint n,
	gfloat ycoeff, gboolean move_cursor 
);
GtkVisibility TViewIsLineVisible(tview_struct *tv, gint n);

gint TViewOpenFile(tview_struct *tv, const gchar *path);
gint TViewSaveFile(tview_struct *tv, const gchar *path);
gint TViewOpenData(tview_struct *tv, const gchar *data, gint data_len);
void TViewClear(tview_struct *tv);

void TViewCut(tview_struct *tv);
void TViewCopy(tview_struct *tv);
void TViewPaste(tview_struct *tv);
void TViewDeleteText(tview_struct *tv);
void TViewSelectAll(tview_struct *tv);
void TViewUnselectAll(tview_struct *tv);

gboolean TViewToplevelIsWindow(tview_struct *tv);
GtkWidget *TViewGetToplevelWidget(tview_struct *tv);
GtkText *TViewGetTextWidget(tview_struct *tv);
GtkMenu *TViewGetMenuWidget(tview_struct *tv);

tview_struct *TViewNew(GtkWidget *parent);
tview_struct *TViewNewWithToplevel(
	gint width, gint height
);
void TViewSetReadOnly(tview_struct *tv, gboolean read_only);
void TViewSetChangedCB(   
	tview_struct *tv,
	void (*changed_cb)(tview_struct *, gpointer),
	gpointer data
);
void TViewUpdate(tview_struct *tv);
void TViewDraw(tview_struct *tv);
void TViewSetBusy(tview_struct *tv, gboolean is_busy);
void TViewMap(tview_struct *tv);
void TViewUnmap(tview_struct *tv);
void TViewDelete(tview_struct *tv);


#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 TVIEW_DEF_TITLE			"Text View"
#define TVIEW_DEF_WIDTH			640
#define TVIEW_DEF_HEIGHT		480


/*
 *	Scrolls the Text View to the specified line.
 */
void TViewScrollToLine(
	tview_struct *tv, gint n,
	gfloat ycoeff, gboolean move_cursor
)
{
	gfloat v;
	gint line_height;
	GdkFont *font;
	GtkAdjustment *adj;
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	font = tv->text_font;
	adj = tv->vadj;
	if((font == NULL) || (adj == NULL))
	    return;

	/* Get height of each line */
	line_height = font->ascent + font->descent;

	/* Calculate scroll position from line number and apply
	 * ycoeff
	 */
	v = (line_height * n) -
	    (ycoeff * MAX(adj->page_size - line_height, 0));

	/* Clip */
	if(v > (adj->upper - adj->page_size))
	    v = adj->upper - adj->page_size;
	if(v < adj->lower)
	    v = adj->lower;

	/* Scroll as needed */
	gtk_adjustment_set_value(adj, v);

	/* Move cursor */
	if(move_cursor)
	{
	    gint i, m = (gint)gtk_text_get_length(GTK_TEXT(w));
	    gint line_count = 0;

	    for(i = 0; i < m; i++)
	    {
		if(line_count == n)
		{
		    gtk_text_set_point(GTK_TEXT(w), i);
		    break;
		}

		if(GTK_TEXT_INDEX(GTK_TEXT(w), i) == '\n')
		    line_count++;
	    }
	}
}

/*
 *	Checks if the specified line on the Text View is visible.
 */
GtkVisibility TViewIsLineVisible(tview_struct *tv, gint n)
{
	gfloat v;
	gint line_height;
	GdkFont *font;
	GtkAdjustment *adj;
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return(GTK_VISIBILITY_NONE);

	font = tv->text_font;
	adj = tv->vadj;      
	if((font == NULL) || (adj == NULL))
	    return(GTK_VISIBILITY_NONE);

	/* Get height of each line */
	line_height = font->ascent + font->descent;
	if(line_height <= 0)
	    return(GTK_VISIBILITY_NONE);

	/* Calculate scroll position from line number */
	v = (n * line_height) - adj->value;

	/* Line completely not visible? */
	if(((v + line_height) <= 0) || (v >= adj->page_size))
	    return(GTK_VISIBILITY_NONE);

	/* Line partially visible? */
	if((v < 0) || ((v + line_height) > adj->page_size))
	    return(GTK_VISIBILITY_PARTIAL);
	else
	    return(GTK_VISIBILITY_FULL);
}

/*
 *	Opens the specified file to the Text View.
 *
 *	TViewClear() will be called first.
 */
gint TViewOpenFile(tview_struct *tv, const gchar *path)
{
	gchar *buf = NULL;
	gint len = 0;
	FILE *fp;
	GtkWidget *w;

	if(tv == NULL)             
	    return(-1);

	/* Delete all text */                        
	TViewClear(tv);

	/* Reset has changes mark */
	if(tv->has_changes)
	    tv->has_changes = FALSE;

	w = tv->text;
	if((w == NULL) || STRISEMPTY(path))
	    return(0);

	/* Open file for reading */
	fp = FOpen(path, "rb");
	if(fp != NULL)
	{
	    struct stat stat_buf;

	    if(!fstat(fileno(fp), &stat_buf))
	    {
		len = stat_buf.st_size;
		if(len > 0)
		    buf = (gchar *)g_malloc(len * sizeof(gchar));
		if(buf != NULL)
		    fread(buf, sizeof(gchar), len, fp);
		else
		    len = 0;
	    }    
	    FClose(fp);
	}

	/* Got data from file? */
	if(buf != NULL)
	{
	    /* Insert text from file data */
	    gtk_text_freeze(GTK_TEXT(w));
	    gtk_text_set_point(GTK_TEXT(w), 0);
	    gtk_text_insert(
		GTK_TEXT(w),
		tv->text_font,
		NULL, NULL,
		buf, len
	    );
	    gtk_text_thaw(GTK_TEXT(w));
	    TViewUpdate(tv);

	    g_free(buf);
	}

	return(0);
}

/*
 *	Saves the data on the Text View to the specified file.
 */
gint TViewSaveFile(tview_struct *tv, const gchar *path)
{
	gchar *buf;
	gint len;
	FILE *fp;
	GtkWidget *w;                

	if(tv == NULL)             
	    return(-1);

	w = tv->text;
	if((w == NULL) || STRISEMPTY(path))
	    return(-1);

	/* Get copy of data from text */
	len = (gint)gtk_text_get_length(GTK_TEXT(w));
	buf = gtk_editable_get_chars(GTK_EDITABLE(w), 0, len);

	/* Open file for writing */
	fp = FOpen(path, "wb");
	if((fp != NULL) && (buf != NULL) && (len > 0))
	{
	    gint i;

	    for(i = 0; i < len; i++)
		fputc(buf[i], fp);
	}

	/* Close file and delete copy of data */
	FClose(fp);
	g_free(buf);

	/* Reset has changes mark */
	if(tv->has_changes) 
	    tv->has_changes = FALSE;

	TViewUpdate(tv);

	return(0);
}

/*
 *	Coppies the given data to the Text View.
 *
 *	TViewClear() will be called first.
 */
gint TViewOpenData(tview_struct *tv, const gchar *data, gint data_len)
{
	GtkWidget *w;

	if(tv == NULL)
	    return(-1);

	/* Delete all text */
	TViewClear(tv);

	/* Reset has changes mark */
	if(tv->has_changes)
	    tv->has_changes = FALSE;

	w = tv->text;
	if((w == NULL) || (data == NULL) || (data_len <= 0))
	    return(0);

	/* Insert text from data */
	gtk_text_freeze(GTK_TEXT(w));
	gtk_text_set_point(GTK_TEXT(w), 0);
	gtk_text_insert(
	    GTK_TEXT(w),
	    tv->text_font,
	    NULL, NULL,
	    data, data_len
	);
	gtk_text_thaw(GTK_TEXT(w));

	TViewUpdate(tv);

	return(0);
}

/*
 *	Clears the Text View.
 */
void TViewClear(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	/* Clear text */
	gtk_text_freeze(GTK_TEXT(w));
	gtk_text_set_point(GTK_TEXT(w), 0);
	gtk_editable_delete_text(GTK_EDITABLE(w), 0, -1);
	gtk_text_thaw(GTK_TEXT(w));

	TViewUpdate(tv);
}


/*
 *	Cuts the selected data on the Text View to the clipboard.
 */ 
void TViewCut(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	if(tv->read_only)
	    return;

	/* Copy selected text to clipboard */
	TViewCopy(tv);

	/* Delete selected text */
	TViewDeleteText(tv);

}

/*
 *	Coppies the selected data on the Text View to the clipboard.
 */
void TViewCopy(tview_struct *tv)
{
	guint8 *buf;
	gint i, len, sel_pos, sel_len;
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	gtk_text_freeze(GTK_TEXT(w));

	/* Get length of data in text */
	len = (gint)gtk_text_get_length(GTK_TEXT(w));

	/* Get selection start position (if any) */
	sel_pos = (gint)GTK_EDITABLE(w)->selection_start_pos;
	if((sel_pos < 0) || (sel_pos >= len))
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}

	/* Calculate selection length (if any) */
	sel_len = (gint)GTK_EDITABLE(w)->selection_end_pos - sel_pos;
	if(sel_len <= 0)
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}

	/* Make sure selection region does not exceed length of data */
	if((sel_pos + sel_len) > len)
	    sel_len = len - sel_pos;
	if(sel_len <= 0)
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}

	/* Allocate buffer and copy selected data to it */
	buf = (guint8 *)g_malloc(sel_len * sizeof(guint8));
	for(i = 0; i < sel_len; i++)
	    buf[i] = (guint8)GTK_TEXT_INDEX(GTK_TEXT(w), sel_pos + i);

	/* Copy to clipboard */
	GUIDDESetBinary(w, GDK_NONE, GDK_CURRENT_TIME, buf, sel_len);

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

	gtk_text_thaw(GTK_TEXT(w));                                   
}

/*
 *	Pastes any data from the clipboard to the Text View.
 */
void TViewPaste(tview_struct *tv)
{
	guint8 *buf;
	gint len;
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	if(tv->read_only)
	    return;

	/* Get copy of data from clipboard */
	buf = GUIDDEGetBinary(
	    w,
	    GDK_NONE,
	    GDK_CURRENT_TIME,
	    &len
	);
#if 0
	/* Paste data to text */
	gtk_text_freeze(GTK_TEXT(w));
	gtk_editable_select_region(
	    GTK_EDITABLE(w), 
	    0,     
	    (gint)gtk_text_get_length(GTK_TEXT(w))     
	);     
	gtk_text_thaw(GTK_TEXT(w));                      
#endif

	/* Delete coppied data from clipboard */
	g_free(buf);

	TViewUpdate(tv);
}

/*
 *	Delete selected text in the Text View.
 */
void TViewDeleteText(tview_struct *tv)
{
	gint len, sel_pos, sel_len;
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	gtk_text_freeze(GTK_TEXT(w));

	/* Get length of data in text */
	len = (gint)gtk_text_get_length(GTK_TEXT(w));

	/* Get selection start position (if any) */
	sel_pos = (gint)GTK_EDITABLE(w)->selection_start_pos;
	if((sel_pos < 0) || (sel_pos >= len))
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}
	 
	/* Calculate selection length (if any) */
	sel_len = (gint)GTK_EDITABLE(w)->selection_end_pos - sel_pos;
	if(sel_len <= 0)
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}

	/* Make sure selection region does not exceed length of data */
	if((sel_pos + sel_len) > len)
	    sel_len = len - sel_pos;
	if(sel_len <= 0)
	{
	    gtk_text_thaw(GTK_TEXT(w));
	    return;
	}

	/* Delete selected text */
	gtk_text_set_point(GTK_TEXT(w), sel_pos);
	gtk_text_forward_delete(GTK_TEXT(w), sel_len);

	gtk_text_thaw(GTK_TEXT(w));

	TViewUpdate(tv);
}

/*
 *	Selects all the text in the Text View.
 */
void TViewSelectAll(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	gtk_text_freeze(GTK_TEXT(w));
	gtk_editable_select_region(
	    GTK_EDITABLE(w),
	    0,
	    (gint)gtk_text_get_length(GTK_TEXT(w))
	);
	gtk_text_thaw(GTK_TEXT(w));

	TViewUpdate(tv);
}

/*
 *	Unselects all the text in the Text View.
 */
void TViewUnselectAll(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	gtk_text_freeze(GTK_TEXT(w));
	gtk_editable_select_region(GTK_EDITABLE(w), 0, 0);
	gtk_text_thaw(GTK_TEXT(w));

	TViewUpdate(tv);
}


/*
 *	Checks if the Text View's toplevel GtkWidget is a GtkWindow.
 */
gboolean TViewToplevelIsWindow(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->toplevel : NULL;
	return((w != NULL) ? GTK_IS_WINDOW(w) : FALSE);
}

/*
 *	Returns the Text View's toplevel GtkWidget.
 */
GtkWidget *TViewGetToplevelWidget(tview_struct *tv)
{
	return((tv != NULL) ? tv->toplevel : NULL);
}

/*
 *	Returns the Text View's GtkText.
 */
GtkText *TViewGetTextWidget(tview_struct *tv)
{
	return((tv != NULL) ? (GtkText *)tv->text : NULL);
}

/*
 *	Returns the Text View's GtkMenu.
 */
GtkMenu *TViewGetMenuWidget(tview_struct *tv)
{
	return((tv != NULL) ? (GtkMenu *)tv->menu : NULL);
}


/*
 *	Creates a new Text View.
 */ 
tview_struct *TViewNew(GtkWidget *parent)
{
	gint border_minor = 2;
	GtkWidget *w, *parent2;
	GtkEditable *editable;
	GtkText *text;
	tview_struct *tv = TVIEW(
	    g_malloc0(sizeof(tview_struct))
	);
	if(tv == NULL)
	    return(NULL);

	/* Reset values */
	tv->map_state = FALSE;
	tv->read_only = FALSE;
	tv->has_changes = FALSE;
	tv->changed_cb = NULL;
	tv->changed_data = NULL;

	/* Load cursors */
	tv->busy_cur = gdk_cursor_new(GDK_WATCH);
	tv->text_cur = gdk_cursor_new(GDK_XTERM);
	tv->translate_cur = gdk_cursor_new(GDK_FLEUR);

	/* Load fonts */
	tv->text_font = gdk_font_load(
"-adobe-courier-medium-r-normal-*-12-*-*-*-m-*-iso8859-1"
	);


	/* Begin creating widgets */

	/* Toplevel */
	tv->toplevel = w = gtk_vbox_new(FALSE, 0);
	if(GTK_IS_BOX(parent))
	    gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	else
	    gtk_container_add(GTK_CONTAINER(parent), w);
	parent = w;

	/* Table for text and scroll bar widgets */
	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;
	/* Text */
	tv->text = w = gtk_text_new(NULL, NULL);
	editable = GTK_EDITABLE(w);                    
	text = GTK_TEXT(w);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(TViewTextEventCB), tv
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(TViewTextEventCB), tv 
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(TViewTextEventCB), tv 
	);
	text->default_tab_width = 8;
	gtk_text_set_editable(text, !tv->read_only);
	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_signal_connect(
	    GTK_OBJECT(w), "changed",   
	    GTK_SIGNAL_FUNC(TViewChangedCB), tv
	);
	gtk_widget_show(w);

	tv->hadj = GTK_TEXT(w)->hadj;
	tv->vadj = GTK_TEXT(w)->vadj;

#if 0
	/* Horizontal scroll bar */
	if(tv->hadj != NULL)
	{
	    GtkAdjustment *adj = tv->hadj;
	    tv->hscrollbar = w = gtk_hscrollbar_new(adj);
	    gtk_table_attach(
		GTK_TABLE(parent2), w,
		0, 1, 1, 2,
		GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		GTK_FILL,
		0, 0
	    );
	    gtk_widget_show(w);
	}
#endif

	/* Vertical scroll bar */
	if(tv->vadj != NULL)
	{
	    GtkAdjustment *adj = tv->vadj;
	    tv->vscrollbar = w = gtk_vscrollbar_new(adj);
	    gtk_table_attach(
		GTK_TABLE(parent2), w,
		1, 2, 0, 1,
		GTK_FILL,
		GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		0, 0
	    );
	    gtk_widget_show(w);
	}


	/* Right-click menu */
	if(TRUE)              
	{
#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                                    \
 );                                             \
}
	    GtkWidget *menu = GUIMenuCreate();
	    guint8 **icon;
	    const gchar *label;
	    gint accel_key;
	    guint accel_mods;
	    GtkAccelGroup *accelgrp = NULL;
	    gpointer mclient_data = tv;
	    void (*func_cb)(GtkWidget *w, gpointer);

	    tv->menu = menu;

	    icon = (guint8 **)icon_cut_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Corte"
#elif defined(PROG_LANGUAGE_FRENCH)
"Coupure"
#elif defined(PROG_LANGUAGE_GERMAN)
"Schnitt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Taglio"
#elif defined(PROG_LANGUAGE_DUTCH)
"Snee"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Corte"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Snitt"
#else
"Cut"
#endif
	    ;
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TViewCutCB;
	    DO_ADD_MENU_ITEM_LABEL
	    tv->cut_mi = w;

	    icon = (guint8 **)icon_copy_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Copia"
#elif defined(PROG_LANGUAGE_FRENCH)
"Copie"
#elif defined(PROG_LANGUAGE_GERMAN)
"Kopie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Copia"
#elif defined(PROG_LANGUAGE_DUTCH)
"Kopie"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Cópia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Kopi"
#else  
"Copy"
#endif 
	    ;
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TViewCopyCB;
	    DO_ADD_MENU_ITEM_LABEL
	    tv->copy_mi = w;

	    icon = (guint8 **)icon_paste_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH) 
"Pasta"
#elif defined(PROG_LANGUAGE_FRENCH)
"Pâte"
#elif defined(PROG_LANGUAGE_GERMAN)
"Paste"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Pasta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Plakmiddel"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Pasta"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Masse"
#else
"Paste"
#endif
	    ;  
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = TViewPasteCB;
	    DO_ADD_MENU_ITEM_LABEL
	    tv->paste_mi = w;

	    icon = (guint8 **)icon_cancel_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer"
#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 = 0;
	    accel_mods = 0;
	    func_cb = TViewDeleteTextCB;
	    DO_ADD_MENU_ITEM_LABEL
	    tv->delete_mi = w;

	    DO_ADD_MENU_SEP

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

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

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP
	}

	return(tv);
}

/*
 *	Creates a new Text View with a toplevel GtkWindow.
 */
tview_struct *TViewNewWithToplevel(
	gint width, gint height
)
{
	tview_struct *tv;
	GdkWindow *window;

	/* Create a toplevel GtkWindow for the Text View */
	GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_widget_set_usize(
	    w,
	    (width > 0) ? width : TVIEW_DEF_WIDTH,
	    (height > 0) ? height : TVIEW_DEF_HEIGHT
	);
	gtk_window_set_title(GTK_WINDOW(w), TVIEW_DEF_TITLE);
#ifdef PROG_NAME
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "textview", PROG_NAME
	);
#endif
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    GUISetWMIcon(
		window,
		(guint8 **)icon_edit_48x48_xpm
	    );
	}
	gtk_container_border_width(GTK_CONTAINER(w), 0);

	/* Create the Text View and parent it to the toplevel
	 * GtkWindow
	 */
	tv = TViewNew(w);

	gtk_widget_show(tv->toplevel);

	/* Change the Text View's toplevel to the newly created
	 * toplevel GtkWindow
	 */
	tv->toplevel = w;

	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(TViewDeleteEventCB), tv
	);                                         

	return(tv);
}

/*
 *	Sets the Text View as read only or read/write.
 */
void TViewSetReadOnly(tview_struct *tv, gboolean read_only)
{
	if(tv == NULL)
	    return;

	if(tv->read_only != read_only)
	{
	    GtkWidget *w = tv->text;
	    if(w != NULL)
	    {
		gtk_text_set_editable(GTK_TEXT(w), !read_only);
	    }

	    tv->read_only = read_only;

	    TViewDraw(tv);
	    TViewUpdate(tv);
	}
}

/*
 *	Sets the Text View's changed callback.
 */
void TViewSetChangedCB(   
	tview_struct *tv,
	void (*changed_cb)(tview_struct *, gpointer),
	gpointer data
)
{
	if(tv == NULL)
	    return;   

	tv->changed_cb = changed_cb;
	tv->changed_data = data;
}

/*
 *	Updates the Text View's widgets to reflect current values.
 */
void TViewUpdate(tview_struct *tv)
{
	gboolean sensitive, read_only, has_selection, has_changes;
	gint sel_len;
	GtkWidget *w;

	if(tv == NULL)
	    return;

	w = tv->text;
	read_only = tv->read_only;
	sel_len = (w != NULL) ?
	    ((gint)GTK_EDITABLE(w)->selection_end_pos -
	     (gint)GTK_EDITABLE(w)->selection_start_pos) : 0;
	has_selection = (sel_len != 0) ? TRUE : FALSE;
	has_changes = tv->has_changes;


	/* Right click menu */

	/* Cut, Copy and Paste */
	sensitive = !read_only ? has_selection : FALSE;
	GTK_WIDGET_SET_SENSITIVE(tv->cut_mi, sensitive)
	sensitive = has_selection;
	GTK_WIDGET_SET_SENSITIVE(tv->copy_mi, sensitive)
	sensitive = !read_only;
	GTK_WIDGET_SET_SENSITIVE(tv->paste_mi, sensitive)
	sensitive = !read_only ? has_selection : FALSE;
	GTK_WIDGET_SET_SENSITIVE(tv->delete_mi, sensitive)

	sensitive = TRUE;
	GTK_WIDGET_SET_SENSITIVE(tv->select_all_mi, sensitive)
	sensitive = has_selection;
	GTK_WIDGET_SET_SENSITIVE(tv->unselect_all_mi, sensitive)
}

/*
 *	Redraws the Text View.
 */
void TViewDraw(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->text : NULL;
	if(w == NULL)
	    return;

	gtk_widget_draw(w, NULL);
}

/*
 *	Sets the Text View as busy or ready.
 */
void TViewSetBusy(tview_struct *tv, gboolean is_busy)
{
#define SET_CURSOR(_w_,_c_)		\
{ if((_w_) != NULL) {			\
 GdkWindow *window = (_w_)->window;	\
 if(window != NULL)			\
  gdk_window_set_cursor(window, (_c_));	\
} }

	if(tv == NULL)
	    return;

	if(is_busy)
	{
	    /* Increase busy count */
	    tv->busy_count++;

	    /* If already busy then don't change anything */
	    if(tv->busy_count > 1)
		return;      

	    SET_CURSOR(tv->toplevel, tv->busy_cur)
	    SET_CURSOR(tv->text, tv->busy_cur)
	}                     
	else                  
	{   
	    /* Reduce busy count */
	    tv->busy_count--;
	    if(tv->busy_count < 0)
		tv->busy_count = 0;

	    /* If still busy do not change anything */
	    if(tv->busy_count > 0)                                     
		return;

	    SET_CURSOR(tv->toplevel, NULL)
	    SET_CURSOR(tv->text, tv->text_cur)
	}

	gdk_flush();

#undef SET_CURSOR
}

/*
 *	Maps the Text View.
 */
void TViewMap(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->toplevel : NULL;
	if(w == NULL)
	    return;

	if(GTK_IS_WINDOW(w))
	    gtk_widget_show_raise(w);
	else
	    gtk_widget_show(w);

	tv->map_state = TRUE;
}

/*
 *	Unmaps the Text View.
 */
void TViewUnmap(tview_struct *tv)
{
	GtkWidget *w = (tv != NULL) ? tv->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
	tv->map_state = FALSE;
}

/*
 *	Deletes the Text View.
 */
void TViewDelete(tview_struct *tv)
{
	if(tv == NULL)
	    return;

	GTK_WIDGET_DESTROY(tv->menu)
	GTK_WIDGET_DESTROY(tv->text)  
	GTK_WIDGET_DESTROY(tv->toplevel)

	GDK_FONT_UNREF(tv->text_font);

	GDK_CURSOR_DESTROY(tv->busy_cur)
	GDK_CURSOR_DESTROY(tv->text_cur)
	GDK_CURSOR_DESTROY(tv->translate_cur)

	g_free(tv);
}
