/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <malloc.h>
#include <Xm/Xm.h>
#include <Xm/ScrollBar.h>
#include <X11/Xatom.h>

#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/ObjectP.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <X11/Composite.h>
#include <X11/Shell.h>

#include <VnsTextP.h>
#include "curs.xbm"
#include "focus_stip.xbm"

#define THICKNESS 20

#define VTW(w) (((VnsTextWidget)(w))->vnsText)

#define MANAGE(w) { if (XtIsRealized(w)) XtMapWidget(w); \
                     (w)->core.managed = True; }

#define UNMANAGE(w) { if (w && XtIsRealized(w)) XtUnmapWidget(w); \
                     (w)->core.managed = False; }
#define IS_MANAGED(w) ((w) && (w)->core.managed)

static Initialize();
static Destroy();
static void Realize();
static Resize();
static Redisplay();
static Boolean SetValues();

static ac_erase_line() ;
static ac_extend() ;
static ac_select() ;
static ac_unselect() ;
static ac_insert_char() ;
static ac_delete_char() ;
static ac_forward_char() ;
static ac_backward_char() ;
static ac_down_line() ;
static ac_up_line() ;
static ac_start_of_line() ;
static ac_end_of_line() ;
static ac_kill_end_of_line() ;
static ac_focus_in() ;
static ac_focus_out() ;
static ac_message() ;
static ac_enter_window() ;
static ac_leave_window() ;
static ac_redraw() ;
static ac_cut() ;
static ac_copy() ;
static ac_paste() ;
static PlaceVnsTextMenu() ;
/*
	<Btn3Down>:     PlaceVnsTextMenu() MenuPopup(EditMenu)\n\
	<Btn3Up>:       MenuPopdown(EditMenu)\n\
*/
static char defaultTranslations[] = "\
	<Btn1Down>:		select()\n\
	<Btn1Motion>:	extend()\n\
	<Btn1Up>:		unselect()\n\
	<Key>osfDelete:	delete()\n\
	<Key>osfBackSpace:	delete()\n\
	<Key>osfLeft:		backward-char()\n\
	<Key>osfRight:		forward-char()\n\
	<Key>osfDown:		down-line()\n\
	<Key>osfUp:		up-line()\n\
	Ctrl<Key>A:		start-of-line()\n\
	Ctrl<Key>B:		backward-char()\n\
	Ctrl<Key>D:		forward-char() delete()\n\
	Ctrl<Key>E:		end-of-line()\n\
	Ctrl<Key>F:		forward-char()\n\
	Ctrl<Key>H:		delete()\n\
	Ctrl<Key>K:		kill-end-of-line()\n\
	Ctrl<Key>L:		redraw()\n\
	Ctrl<Key>N:		down-line()\n\
	Ctrl<Key>P:		up-line()\n\
	Ctrl<Key>U:		erase-line()\n\
	Ctrl<Key>W:		cut()\n\
	Ctrl<Key>Y:		paste()\n\
	<Key>L6:		copy()\n\
	<Key>L8:		paste()\n\
	<Key>L10:		cut()\n\
	<Key>:			insert-char()\n\
	<Message>:		message()\n\
	<FocusIn>:		focus-in()\n\
	<FocusOut>:		focus-out()\n" ;

static XtActionsRec actionsList[] =
{
	{"select",(XtActionProc)ac_select},
	{"unselect",(XtActionProc)ac_unselect},
	{"extend",(XtActionProc)ac_extend},
	{"insert-char",(XtActionProc)ac_insert_char},
	{"delete",(XtActionProc)ac_delete_char},
	{"forward-char",(XtActionProc)ac_forward_char},
	{"backward-char",(XtActionProc)ac_backward_char},
	{"down-line",(XtActionProc)ac_down_line},
	{"up-line",(XtActionProc)ac_up_line},
	{"start-of-line",(XtActionProc)ac_start_of_line},
	{"erase-line",(XtActionProc)ac_erase_line},
	{"end-of-line",(XtActionProc)ac_end_of_line},
	{"kill-end-of-line",(XtActionProc)ac_kill_end_of_line},
	{"focus-in",(XtActionProc)ac_focus_in},
	{"focus-out",(XtActionProc)ac_focus_out},
	{"message",(XtActionProc)ac_message},
	{"redraw",(XtActionProc)ac_redraw},
	{"cut",(XtActionProc)ac_cut},
	{"copy",(XtActionProc)ac_copy},
	{"paste",(XtActionProc)ac_paste},
	{"PlaceVnsTextMenu",(XtActionProc)PlaceVnsTextMenu},
} ;

static XtResource resources[] =
{
	{VnsNdrag,
		VnsCDrag,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsTextWidget, vnsText.drag),
		XtRString,
		"FALSE"},

	{XtNreadOnly,
		XtCReadOnly,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsTextWidget, vnsText.read_only),
		XtRString,
		"FALSE"},

	{XtNforeground,
		XtCForeground,
		XtRPixel,
		sizeof(Pixel),
		XtOffset(VnsTextWidget, vnsText.foreground),
		XtRString,
		XtDefaultForeground},

	{XtNstring,
		XtCString,
		XtRString,
		sizeof(caddr_t),
		XtOffset(VnsTextWidget, vnsText.str),
		XtRString,
		NULL},

	{VnsNtxtCursor,
		XtCPixmap,
		XtRBitmap,
		sizeof(Pixmap),
		XtOffset(VnsTextWidget, vnsText.curs),
		XtRBitmap,
		None},

	{VnsNtxtCursorHotX,
		VnsCTxtCursorHotX,
		XtRInt,
		sizeof(int),
		XtOffset(VnsTextWidget, vnsText.curs_hot_x),
		XtRString,
		"0"},

	{VnsNtxtCursorHotY,
		VnsCTxtCursorHotY,
		XtRInt,
		sizeof(int),
		XtOffset(VnsTextWidget, vnsText.curs_hot_y),
		XtRString,
		"0"},

	{XtNfont,
		XtCFont,
		XtRFontStruct,
		sizeof(caddr_t),
		XtOffset(VnsTextWidget, vnsText.font),
		XtRString,
		"variable"},
} ;

VnsTextClassRec vnsTextWidgetClassRec =
{
	{
		(WidgetClass)&widgetClassRec,			/* superclass */
		"VnsText",								/* class name */
		sizeof(VnsTextRec),						/* widget size */
		NULL,									/* class_initialize */
		NULL,									/* class_part_initialize */
		FALSE,									/* class inited */
		(XtInitProc)Initialize,					/* initialize */
		NULL,									/* initialize_hook */
		Realize,								/* realize */
		actionsList,							/* actions */
		XtNumber(actionsList),					/* num_actions */
		resources,								/* resources */
		XtNumber(resources),					/* num_resources */
		NULLQUARK,								/* xrm_class */
		TRUE,									/* compress_motion */
		TRUE,									/* compress_exposure */
		FALSE,									/* compress_enterleave */
		FALSE,									/* visible_interest */
		(XtWidgetProc)Destroy,					/* destroy */
		(XtWidgetProc)Resize,					/* resize */
		(XtExposeProc)Redisplay,				/* expose */
		SetValues,								/* set_values */
		NULL,									/* set_values_hook */
		XtInheritSetValuesAlmost,				/* set_values_almost */
		NULL,									/* get_values_hook */
		NULL,									/* accept_focus */
		XtVersion,								/* version */
		NULL,									/* callback private */
		defaultTranslations,					/* tm_table */
		NULL,									/* query_geometry */
		XtInheritDisplayAccelerator,			/* display_accelerator */
		NULL									/* extension */
	},
	{
		0,
	}
} ;

WidgetClass vnsTextWidgetClass = (WidgetClass)&vnsTextWidgetClassRec ;

/*
 * scrollbar callback function when thumb has moved.
 */
static
void
ScrollCallback(vbar, client_data, info)
	Widget vbar;
	XtPointer client_data;
	XmScrollBarCallbackStruct *info;
{
	Widget w = XtParent(vbar);
	VnsTextPart *vtw = &VTW(w);

	/* erase the old cursor */
	erase_cursor(w);

	/* set the new top row */
	vtw->top_row = info->value;

	/* redisplay the window */
	redisplay_window(w);
}

/*
 * This function organizes all necessary calculations in order to
 * propertly format the text in the window and activate and deactivate
 * the vertical scrollbar. 
 */
static
set_up_offsets(w)
	Widget w;
{
	VnsTextPart *vtw = &VTW(w);
	int shift = False;
	int req_height;
	int scrollmax;
	int scrollval;
	int ndisp;
	int size;

	/* calculate the offsets depending on scrollbar state */
	if (IS_MANAGED(vtw->vbar))
	{
		size = THICKNESS + 5;
		calc_offsets(w,size);
	}
	else
	{
		calc_offsets(w,0);
	}

	/* calculate the required height to display all of the lines */
	req_height = vtw->nlines * vtw->fhgt;

	/* if required height is larger than window, then we need the scrollbar */
	if (req_height > w->core.height)
	{
		/* if the scrollbar is not managed, then we must recalculate taking
		 * into account the size of the scrollbar placed to the right.
		 */
		if (!XtIsManaged(vtw->vbar))
		{
			size = THICKNESS + 5;
			calc_offsets(w,size);
		}

		/* determine the number of lines that can be displayed */
		ndisp = w->core.height / vtw->fhgt;
		scrollmax = vtw->nlines - ndisp + 1;

		if (IS_MANAGED(vtw->vbar))
		{
			int smax;
			int sval;
			int size;

			/* obtain the current scroll value and max value for the scrollbar */
			XtVaGetValues(vtw->vbar,
				XmNvalue, &sval,
				XmNmaximum, &smax,
				NULL);

			/* if we are scrolled to the bottom, then set top row correctly */
			if ((sval == smax) && (vtw->top_row != scrollmax))
			{
				vtw->top_row = scrollmax;
				shift = True;
			}

			/* calculate the slider size */
			if (ndisp > 0)
			{
				size = scrollmax / ndisp;
			}
			else
			{
				size = scrollmax;
			}

			if (size < 1)
			{
				size = 1;
			}
			else if (size > scrollmax)
			{
				size = scrollmax;
			}

			/* update the maximum value for the scrollbar */
			XtVaSetValues(vtw->vbar,
				XmNmaximum, scrollmax,
				XmNsliderSize, size,
				NULL);
		}
		else
		{
			int size;

			/* calculate the slider size */
			if (ndisp > 0)
			{
				size = scrollmax / ndisp;
			}
			else
			{
				size = scrollmax;
			}

			if (size < 1)
			{
				size = 1;
			}
			else if (size > scrollmax)
			{
				size = scrollmax;
			}

			/* we are activating scrollbar, so top row is reset */
			vtw->top_row = 0;

			/* update the maximum value for the scrollbar and reset scroll value */
			XtVaSetValues(vtw->vbar,
				XmNmaximum, scrollmax,
				XmNminimum, 0,
				XmNsliderSize,size,
				XmNvalue, 0,
				NULL);

			redisplay_window(w);
		}

		/* position the scrollbar in the window */
		if (XtIsRealized(vtw->vbar))
		{
			XtConfigureWidget(vtw->vbar,
				w->core.width - THICKNESS - 1, 0,
				THICKNESS, w->core.height,
				1);
		}

		/* realize the scrollbar */
		if (XtIsRealized(w))
			XtRealizeWidget(vtw->vbar);

		/* manage scrollbar */
		MANAGE(vtw->vbar) ;
	}
	else
	{
		/* if scrollbar is active, then hide it */
		if (IS_MANAGED(vtw->vbar))
		{
			/* unmanage the scrollbar */
			UNMANAGE(vtw->vbar);

			/* reset the top row index to the first line (zero) */
			vtw->top_row = 0;

			/* recalculate the offsets without the scrollbar */
			calc_offsets(w,0);

			/* always recalculate after desctivating the scrollbar */
			shift = True;
		}
	}

	return shift;
}

/*
 * Recopies the string and computes the offsets for each line.
 * The offset parameter is subtracted from the width of the window
 * so the scrollbar width can be taken into account.
 */
static
calc_offsets(w,offset)
	Widget w ;
	int offset ;
{
	char *s = VTW(w).str ;
	VTW(w).nlines = 0 ;
	for(;;)
	{
		int nwords = 0 ;
		char *line = s ;
		char *end ;
		for(;;)
		{
			char *t ;

			/* Munch the spaces */
			while(*s == ' ' || *s == '\t')
			{
				s++ ;
			}

			/* Save beginning of word */
			t = s ;
	
			/* Get the chars that make up the word */
			while(*s != ' ' && *s != '\t' && *s && *s != '\n')
			{
				s++ ;
			}
	
			/* Add to word count */
			nwords++ ;
	
			/* If we have overrun the line */
			if (text_width(w,line,s - line,0) > w->core.width - offset)
			{
				/* If there was only one word on the line then do the best we can */
				if (nwords == 1)
				{
					end = (*s == ' ' || *s == '\t') ? (s - 1) : s ;
				}

				/* Otherwise draw up to the last word */
				else
				{
					end = t - 1 ;
					s = t ;
				}
				break ;
			}
	
			/* If new line or end of string then draw out entire line */
			else if (*s != ' ' && *s != '\t')
			{
				end = s ;
				break ;
			}
		}
		{
			int i = VTW(w).nlines++ ;
			int size = VTW(w).nlines * sizeof(Line) ;
			if (size > VTW(w).mlines)
			{
				if (VTW(w).lines == NULL)
				{
					VTW(w).lines = (Line *)malloc(size) ;
				}
				else
				{
					VTW(w).lines = (Line *)realloc((char *)VTW(w).lines,size) ;
				}
				VTW(w).mlines = size ;
			}
			VTW(w).lines[i].pos = line - VTW(w).str ;
			VTW(w).lines[i].len = end - line + 1 ;
		}
		if (!*s)
		{
			return ;
		}
		if (*s == '\n')
		{
			s++ ;
		}
	} ;
}

/*
 * set the width and height of the cursor
 */
static
set_curs(w)
	Widget w ;
{
	Window root ;
	int x,y ;
	unsigned width,height,bw,depth ;

	/* get the geometry for the cursor */
	XGetGeometry(XtDisplay(w),VTW(w).curs,
		&root,&x,&y,&width,&height,&bw,&depth) ;

	/* set the width and height of the cursor */
	VTW(w).curs_h = height ;
	VTW(w).curs_w = width ;
}

/*
 * Callback for Cut menu selection.
 */
/*ARGSUSED*/
static
void
sel_cut(w,text)
	Widget w;
	Widget text;
{
	ac_cut(text);
}

/*
 * Callback for Copy menu selection.
 */
/*ARGSUSED*/
static
void
sel_copy(w,text)
	Widget w;
	Widget text;
{
	ac_copy(text);
}

/*
 * Callback for Paste menu selection.
 */
/*ARGSUSED*/
static
void
sel_paste(w,text)
	Widget w;
	Widget text;
{
	ac_paste(text);
}

/*
 * place the menu at the current pointer position
 */
/*ARGSUSED*/
static
PlaceVnsTextMenu(w,event)
	Widget w ;
	XButtonEvent *event;
{
	Widget menu = XtNameToWidget(w,"*EditMenu");
	Widget cut = XtNameToWidget(w,"*EditMenu.Cut");
	Widget copy = XtNameToWidget(w,"*EditMenu.Copy");
	Widget paste = XtNameToWidget(w,"*EditMenu.Paste");
	int hl = VTW(w).highlight_length;

	/* set the location of the menu */
	XtVaSetValues(menu,
		XtNx,event->x_root,
		XtNy,event->y_root,
		NULL);

	/* check highlight status and set sensitivities accordingly */
	if (hl > 0)
	{
		XtVaSetValues(cut,
			XtNsensitive,True,
			NULL);
		XtVaSetValues(copy,
			XtNsensitive,True,
			NULL);
	}
	else
	{
		XtVaSetValues(cut,
			XtNsensitive,False,
			NULL);
		XtVaSetValues(copy,
			XtNsensitive,False,
			NULL);
	}
}

/*
 * Widget Initialization function
 */
/*ARGSUSED*/
static
Initialize(request,new)
	Widget request,new ;
{
	VnsTextPart *vtw = &VTW(new) ;
	XFontStruct *f = vtw->font ;

	if (new->core.width <= 0)
	{
		new->core.width = 1;
	}
	if (new->core.height <= 0)
	{
		new->core.height = 1;
	}

	/* create the default cursor */
	vtw->def_curs = XCreateBitmapFromData(XtDisplay(new),DefaultRootWindow(XtDisplay(new)),
		curs_bits,curs_width,curs_height) ;

	/* if no cursor, then set it equal to the default cursor */
	if (vtw->curs == None)
	{
		vtw->curs = vtw->def_curs ;
		vtw->curs_hot_x = curs_x_hot ;
		vtw->curs_hot_y = curs_y_hot ;
	}

	vtw->focus_stip = XCreateBitmapFromData(XtDisplay(new),DefaultRootWindow(XtDisplay(new)),
		focus_stip_bits,focus_stip_width,focus_stip_height) ;

	/* set the cursor width and height based on cursor defined */
	set_curs(new) ;

	vtw->font = f ;
	vtw->fhgt = f->ascent + f->descent ;
	vtw->have_focus = FALSE ;

	vtw->lines = NULL ;
	vtw->nlines = 0 ;
	vtw->mlines = 0 ;
	VTW(new).str = strdup((VTW(new).str == NULL) ? "" : VTW(new).str) ;
	VTW(new).highlight_begin = 0 ;
	VTW(new).highlight_length = 0 ;
	vtw->vbar = (Widget)NULL;
	vtw->top_row = 0;
	vtw->cutbuf = strdup("");
	vtw->use_buffer = FALSE;

	/* create the vetical scrollbar */
	VTW(new).vbar = XtVaCreateWidget("vbar",xmScrollBarWidgetClass,new,
		XmNmaximum,125,
		NULL);
	XtAddCallback(VTW(new).vbar, XmNvalueChangedCallback, ScrollCallback, (XtPointer)NULL);

	/* Set up GC's */
	make_gcs(new) ;

	vtw->is_extending = False ;
	vtw->is_dragging = False ;
	vtw->n_select = 0 ;
	{
		Display *dpy = XtDisplay(new) ;
		int screen = DefaultScreen(dpy) ;
		XGCValues values ;
		Pixel white = WhitePixel(dpy,screen) ;
		Pixel black = BlackPixel(dpy,screen) ;
		values.foreground = black ^ white ;
		values.background = white ;
		values.line_width = 0 ;
		values.function = GXxor ;
		values.plane_mask = values.foreground ;
		values.subwindow_mode = IncludeInferiors ;
		vtw->drag_gc = XtGetGC(new,
                (unsigned long)GCForeground |
                (unsigned long)GCBackground |
                (unsigned long)GCLineWidth  |
                (unsigned long)GCSubwindowMode |
                (unsigned long)GCPlaneMask  |
                (unsigned long)GCFunction,
                &values);
	}
}

/*ARGSUSED*/
static
Destroy(w)
	Widget w ;
{
	free(VTW(w).str) ;
	release_gcs(w) ;
	XtReleaseGC(w,VTW(w).drag_gc) ;
	XFreePixmap(XtDisplay(w),VTW(w).def_curs);
	XFreePixmap(XtDisplay(w),VTW(w).focus_stip);
	XtDestroyWidget(VTW(w).vbar);
}

/*ARGSUSED*/
static
void
Realize(w, mask, attr)
	Widget w ;
	Mask *mask;
	XSetWindowAttributes *attr;
{
	register VnsTextPart *vtw = &VTW(w);

	XtCreateWindow(w,CopyFromParent,CopyFromParent, *mask, attr);

	if (vtw->vbar != NULL)
	{
		XtRealizeWidget(vtw->vbar);
		set_up_offsets(w);
	}

	if (IS_MANAGED(vtw->vbar))
	{
		XtMapWidget(vtw->vbar);
	}
}

/*ARGSUSED*/
static
Resize(w)
	Widget w ;
{
	if (XtIsRealized(w))
	{
		set_up_offsets(w);
		redisplay_window(w);
	}
}

static
release_gcs(w)
	Widget w ;
{
	XtReleaseGC(w,VTW(w).gc_fore) ;
	XtReleaseGC(w,VTW(w).gc_back) ;
	XFreeGC(XtDisplay(w),VTW(w).curs_gc) ;
}

static
make_gcs(w)
	Widget w ;
{
	Pixel white = w->core.background_pixel ;
	Pixel black = VTW(w).foreground ;
	XtGCMask mask = (unsigned)GCForeground | (unsigned)GCBackground |
		(unsigned)GCFont ;
	XGCValues values ;
	values.foreground = black ;
	values.background = white ;
	values.font = VTW(w).font->fid ;
	VTW(w).gc_fore = XtGetGC(w,mask,&values) ;
	values.foreground = white ;
	values.background = black ;
	VTW(w).gc_back = XtGetGC(w,mask,&values) ;
	VTW(w).curs_gc = XCreateGC(XtDisplay(w),
		DefaultRootWindow(XtDisplay(w)),
		0L,
		&values) ;
	XSetStipple(XtDisplay(w),VTW(w).curs_gc,VTW(w).focus_stip) ;
	if (VTW(w).have_focus)
	{
		XSetFillStyle(XtDisplay(w),VTW(w).curs_gc,FillStippled) ;
	}
	XSetForeground(XtDisplay(w),VTW(w).curs_gc,black) ;
	XSetClipMask(XtDisplay(w),VTW(w).curs_gc,VTW(w).curs) ;
}

/*
 * finds the width of a text string taking tabs into account.
 */
static
text_width(w,s,l,x)
	Widget w ;
	char *s ;
{
	if (l < 1)
	{
		return 0 ;
	}
	if (s[l - 1] == '\n' || !s[l - 1])
	{
		l-- ;
	}
	{
		int width = 0 ;
		int len_tab = VTW(w).font->max_bounds.width * 8 ;
		while(l)
		{
			int i = 0 ;
			while(i < l && s[i] != '\t')
			{
				i++ ;
			}
			{
				int ww = XTextWidth(VTW(w).font,s,i) ;
				width += ww ;
				x += ww ;
			}
			if (i < l)
			{
				int nx = (x / len_tab + 1) * len_tab ;
				width += nx - x ;
				x = nx ;
				i++ ;
			}
			s += i ;
			l -= i ;
		}
		return width ;
	}
}

/*
 * draw a string 
 */
static
draw_string(w,gc,x,y,s,l)
	Widget w ;
	GC gc ;
	char *s ;
{
	int len_tab = VTW(w).font->max_bounds.width * 8 ;
	while(l)
	{
		int i = 0 ;
		while(i < l && s[i] != '\t')
		{
			i++ ;
		}
		XDrawString(XtDisplay(w),XtWindow(w),gc,x,y + VTW(w).font->ascent,s,i) ;
		x += XTextWidth(VTW(w).font,s,i) ;
		if (i < l)
		{
			x = (x / len_tab + 1) * len_tab ;
			i++ ;
		}
		s += i ;
		l -= i ;
	}
}

/*
 * redisplay the rest of the window below the text (till EOF).
 * Note that this function assumes no scrollbars.
 */
static
redisplay_eof(w)
	Widget w ;
{
	int n = VTW(w).nlines - VTW(w).top_row;
	int ny = n * VTW(w).fhgt ;
	XClearArea(XtDisplay(w),XtWindow(w),0,ny,0,0,False) ;
}

static
dportion(w,s,t,x,y,gc,gc2,clip_beg,clip_end,eol_pos)
	Widget w ;
	char *s ;
	char *t ;
	GC gc ;
	GC gc2 ;
	char *clip_beg ;
	char *clip_end ;
	char *eol_pos ;
{
	char *beg ;
	char *end ;
	if (s < clip_beg)
	{
		x += text_width(w,s,clip_beg - s,x) ;
		beg = clip_beg ;
	}
	else
	{
		beg = s ;
	}
	if (t > clip_end)
	{
		end = clip_end ;
	}
	else
	{
		end = t ;
	}
	{
		int l = t - s + 1 ;
		int l2 = end - beg + 1 ;
		if (l2 > 0)
		{
			int filled_to_eol = (beg + l2 - 1 == eol_pos) ;
			if (beg[l2 - 1] == '\n' || !beg[l2 - 1])
			{
				l2-- ;
			}
			{
				int tw = text_width(w,beg,l2,x) ;
				XFillRectangle(XtDisplay(w),XtWindow(w),gc2,x,y,tw,
					VTW(w).fhgt) ;
				if (filled_to_eol)
				{
					XClearArea(XtDisplay(w),XtWindow(w),x + tw,y,0,
						VTW(w).fhgt,False) ;
				}
			}
			draw_string(w,gc,x,y ,beg,l2) ;
			return l ;
		}
		return 0 ;
	}
}

/* Displays a line */
static
redisplay_line_portion(w,i,beg_pos,end_pos)
	Widget w ;
	char *beg_pos ;
	char *end_pos ;
{
	if (XtIsRealized(w))
	{
		int y ;
		char *line ;
		char *end ;
		int hl = VTW(w).highlight_length ;
		int ip;

		/* determine if line can be displayed */
		ip = i - VTW(w).top_row;
		if (ip < 0) return;
		y = ip * VTW(w).fhgt;
		if (y > w->core.height) return;

		line = VTW(w).str + VTW(w).lines[i].pos ;
		end = line + VTW(w).lines[i].len - 1 ;
		if (hl)
		{
			int hb = VTW(w).highlight_begin ;
			int he = hb + hl - 1 ;
			if (VTW(w).str + hb < line)
			{
				hb = line - VTW(w).str ;
			}
			if (VTW(w).str + he > end)
			{
				he = end - VTW(w).str ;
			}
			if (hb <= he)
			{
				char *s = line ;
				int x = 0 ;
				int l = dportion(w,s,VTW(w).str + hb - 1,x,y,VTW(w).gc_fore,VTW(w).gc_back,beg_pos,end_pos,end) ;
				x += text_width(w,s,l,x) ;
				s += l ;
				l = dportion(w,s,VTW(w).str + he,x,y,VTW(w).gc_back,VTW(w).gc_fore,beg_pos,end_pos,end) ;
				x += text_width(w,s,l,x) ;
				s += l ;
				dportion(w,s,end,x,y,VTW(w).gc_fore,VTW(w).gc_back,beg_pos,end_pos,end) ;
				return ;
			}
		}
		dportion(w,line,end,0,y,VTW(w).gc_fore,VTW(w).gc_back,beg_pos,end_pos,end) ;
	}
}

/* Displays a line */
static
redisplay_line(w,i)
	Widget w ;
{
	char *s = VTW(w).str + VTW(w).lines[i].pos ;
	redisplay_line_portion(w,i,s,s + VTW(w).lines[i].len - 1) ;
}

/* Displays a porition of a string */
static
redisplay_portion(w,beg,end)
	Widget w ;
{
	int l1 = find_line(w,beg) ;
	int l2 = find_line(w,end) ;
	if (l1 == l2)
	{
		redisplay_line_portion(w,l1,VTW(w).str + beg,VTW(w).str + end) ;
	}
	else
	{
		int i ;
		redisplay_line_portion(w,l1,VTW(w).str + beg,VTW(w).str + VTW(w).lines[l1].pos + VTW(w).lines[l1].len - 1) ;
		for(i = l1 + 1; i < l2; i++)
		{
			redisplay_line(w,i) ;
		}
		redisplay_line_portion(w,l2,VTW(w).str + VTW(w).lines[l2].pos,VTW(w).str + end) ;
	}
}

/*
 * This function takes the position in the main text string (vtw->str) and
 * returns the internal line index where that position occurrs.
 */ 
static
find_line(w,pos)
	Widget w ;
{
	int j ;
	for(j = 0; j < VTW(w).nlines - 1; j++)
	{
		if (pos < VTW(w).lines[j + 1].pos)
		{
			return j ;
		}
	}
	return j ;
}

/*
 * This function takes the internal index (or y position) and 
 * determines the x position in pixels.
 */
static
find_x_pos(w,i,px,round)
	Widget w ;
{
	char *line = VTW(w).str + VTW(w).lines[i].pos ;
	char *end = line + VTW(w).lines[i].len - 1 ;
	char *p ;
	int x = 0 ;
	for(p = line; p <= end; p++)
	{
		int nx = text_width(w,line,p - line + 1,0) ;
		if (!round && px < (x + nx) / 2 ||
		    round > 0 && px < x ||
			round < 0 && px < nx)
		{
			return p - VTW(w).str ;
		}
		x = nx ;
	}
	return end - VTW(w).str ;
}

static
find_y_pos(w,py)
	Widget w ;
{
	VnsTextPart *vtw = &VTW(w) ;
	int wi = py / vtw->fhgt ;
	int ip;

	if (vtw->nlines == 0)
	{
		return 0;
	}

	if (wi < 0)
	{
		ip = vtw->top_row ;
	}
	else if (wi >= vtw->nlines)
	{
		ip = vtw->nlines - 1 ;
	}
	else
	{
		ip = vtw->top_row + wi ;
	}
	return ip;
}

static
find_xy_pos(w,px,py,round)
	Widget w ;
{
	int i = find_y_pos(w,py) ;
	return find_x_pos(w,i,px,round) ;
}

static
redisplay_box(w,x,y,width,height)
	Widget w ;
{
	int l_beg = find_y_pos(w,y) ;
	int l_end = l_beg + (height / VTW(w).fhgt);
	int i;

	/* this is a hack */
	if ((l_beg == l_end) && (height > 0))
	{
		l_end = l_beg + 1;
	}

	i = l_beg;
	while(i <= l_end && i < VTW(w).nlines)
	{
		int p1 = find_x_pos(w,i,x,-1) ;
		int p2 = find_x_pos(w,i,x + width - 1,1) ;
		redisplay_line_portion(w,i,VTW(w).str + p1,VTW(w).str + p2) ;
		i++ ;
	}
	if (l_end >= VTW(w).nlines)
	{
		redisplay_eof(w) ;
	}
}

static
redisplay_window(w)
	Widget w;
{
	int pos = VTW(w).highlight_begin;
	redisplay_box(w,0,0,w->core.width,w->core.height);
	place_cursor(w,pos);
}

/*ARGSUSED*/
static
Redisplay(w, event)
	Widget w ;
	XExposeEvent *event ;
{
	redisplay_box(w,event->x,event->y,event->width,event->height) ;
	draw_cursor(w) ;
}

/*ARGSUSED*/
static
Boolean
SetValues(old, request, new)
	Widget old ;
	Widget request ;
	Widget new ;
{
	VnsTextPart *vtw = &VTW(new) ;

	if (new->core.width <= 0)
	{
		new->core.width = 1;
	}
	if (new->core.height <= 0)
	{
		new->core.height = 1;
	}

	if (VTW(old).curs != VTW(new).curs)
	{
		if (vtw->curs == None)
		{
			vtw->curs = vtw->def_curs ;
			vtw->curs_hot_x = curs_x_hot ;
			vtw->curs_hot_y = curs_y_hot ;
		}
		set_curs(new) ;
	}
	if (VTW(new).str != VTW(old).str || VTW(old).font != VTW(new).font)
	{
		if (VTW(old).font != VTW(new).font)
		{
			VTW(new).fhgt = VTW(new).font->ascent + VTW(new).font->descent ;
		}
		if (VTW(old).str != VTW(new).str)
		{
			VTW(new).str = strdup((VTW(new).str == NULL) ? "" : VTW(new).str) ;
			VTW(new).highlight_begin = 0 ;
			VTW(new).highlight_length = 0 ;
			free(VTW(old).str) ;
		}
		set_up_offsets(new) ;
	}
	if (VTW(old).font->fid != VTW(new).font->fid ||
		old->core.background_pixel != new->core.background_pixel ||
		VTW(old).foreground != VTW(new).foreground)
	{
		release_gcs(new) ;
		make_gcs(new) ;
	}
	return True ;
}

static
place_cursor(w,pos)
	Widget w ;
{
	highlight_text(w,pos,0) ;
}

static
erase_cursor(w)
	Widget w ;
{
	VnsTextPart *vtw = &VTW(w) ;
	int ip,x,y,rx,ry;

	int pos = vtw->highlight_begin ;
	int ln = find_line(w,pos) ;

	ip = ln - vtw->top_row;
	if (ip < 0) return;
	y = ip * VTW(w).fhgt;
	if (y > w->core.height) return;

	x = text_width(w,VTW(w).str + vtw->lines[ln].pos,
		pos - vtw->lines[ln].pos,0) ;
	rx = x - vtw->curs_hot_x ;
	ry = y - vtw->curs_hot_y + vtw->font->ascent ;

	redisplay_box(w,rx,ry,vtw->curs_w,vtw->curs_h) ;
}

static
draw_cursor(w)
	Widget w ;
{
	if (!VTW(w).read_only && !VTW(w).highlight_length)
	{
		int ip,x,y,rx,ry;

		int pos = VTW(w).highlight_begin ;
		int ln = find_line(w,pos) ;

		ip = ln - VTW(w).top_row;
		if (ip < 0) return;
		y = ip * VTW(w).fhgt;
		if (y > w->core.height) return;

		x = text_width(w,VTW(w).str + VTW(w).lines[ln].pos,
			pos - VTW(w).lines[ln].pos,0) ;
		rx = x - VTW(w).curs_hot_x ;
		ry = y - VTW(w).curs_hot_y + VTW(w).font->ascent ;

		XSetClipOrigin(XtDisplay(w),VTW(w).curs_gc,rx,ry) ;
		XFillRectangle(XtDisplay(w),XtWindow(w),VTW(w).curs_gc,
			rx,ry,VTW(w).curs_w,VTW(w).curs_h) ;
	}
}

static
Convert(w,selection,target,type,value,length,format)
	Widget w ;
	Atom *selection ;
	Atom *target ;
	Atom *type ;
	XtPointer *value ;
	unsigned long *length ;
	int *format ;
{
	if (*target == XInternAtom(XtDisplay(w),"TARGETS",False))
	{
		Atom *targets ;
		XmuConvertStandardSelection(w,CurrentTime,selection,
			target,type,value,length,format) ;
		*length += 2 ;
		targets = (Atom *)(*value = XtRealloc((Atom *)*value,*length * sizeof(Atom))) ;
		targets[*length - 1] = XA_STRING ;
		targets[*length - 2] = XInternAtom(XtDisplay(w),"TEXT",False) ;
		return True ;
	}
	else if (VTW(w).own_selection && 
		(*target == XA_STRING || *target == XInternAtom(XtDisplay(w),"TEXT",False)))
	{
		int hl = VTW(w).highlight_length ;
		char *s = VTW(w).str + VTW(w).highlight_begin ;
		if (hl && !s[hl - 1])
		{
			hl-- ;
		}
		*value = XtMalloc(hl + 1) ;
		strncpy(*value,s,hl);
		*type = *target ;
		*length = hl ;
		*format = 8 ;
		return True ;
	}
	else if (VTW(w).own_secondary && 
		(*target == XA_STRING || *target == XInternAtom(XtDisplay(w),"TEXT",False)))
	{
		*type = *target ;
		*format = 8 ;
		*length = strlen(VTW(w).cutbuf) ;
		*value = XtNewString(VTW(w).cutbuf) ;
		return True ;
	}
	else
	{
		return XmuConvertStandardSelection(w,CurrentTime,
			selection,target,type,value,length,format) ;
	}
}

static
Lose(w)
	Widget w ;
{
	VTW(w).own_selection = FALSE ;
	place_cursor(w,VTW(w).highlight_begin) ;
}

static
own_selection(w)
	Widget w ;
{
	if (VTW(w).highlight_length)
	{
		if (XtOwnSelection(w,XA_PRIMARY,CurrentTime,Convert,Lose,NULL))
		{
			VTW(w).own_selection = TRUE ;
		}
	}
	else
	{
		disown_selection(w) ;
	}
}

static
disown_selection(w)
	Widget w ;
{
	if (VTW(w).own_selection)
	{
		XtDisownSelection(w,XA_PRIMARY,CurrentTime) ;
		VTW(w).own_selection = FALSE ;
	}
}

static
LoseBuffer(w)
	Widget w ;
{
	VTW(w).own_secondary = FALSE ;
}

static
own_buffer(w)
	Widget w;
{
	VTW(w).own_secondary = TRUE;
	XtOwnSelection(w,XA_SECONDARY,CurrentTime,Convert,LoseBuffer,NULL);
}

static
highlight_text(w,hb,hl)
	Widget w ;
{
	int he = hb + hl - 1 ;
	int ohb = VTW(w).highlight_begin ;
	int ohe = ohb + VTW(w).highlight_length - 1 ;
	if (hb == ohb && he == ohe)
	{
		return ;
	}
	if (!VTW(w).highlight_length)
	{
		erase_cursor(w) ;
	}
	VTW(w).highlight_begin = hb ;
	VTW(w).highlight_length = hl ;
	if (!hl)
	{
		redisplay_portion(w,ohb,ohe) ;
		draw_cursor(w) ;
	}
	else if (hb < ohb)
	{
		if (he < ohb)
		{
			redisplay_portion(w,hb,he) ;
			redisplay_portion(w,ohb,ohe) ;
		}
		else
		{
			redisplay_portion(w,hb,ohb) ;
			if (he < ohe)
			{
				redisplay_portion(w,he,ohe) ;
			}
			else
			{
				redisplay_portion(w,ohe,he) ;
			}
		}
	}
	else
	{
		if (hb > ohe)
		{
			redisplay_portion(w,hb,he) ;
			redisplay_portion(w,ohb,ohe) ;
		}
		else
		{
			redisplay_portion(w,ohb,hb) ;
			if (he < ohe)
			{
				redisplay_portion(w,he,ohe) ;
			}
			else
			{
				redisplay_portion(w,ohe,he) ;
			}
		}
	}
}

static
draw_lines(w)
	Widget w ;
{
	if (VTW(w).drag)
	{
		XDrawRectangles(XtDisplay(w),DefaultRootWindow(XtDisplay(w)),VTW(w).drag_gc,
			VTW(w).rects,VTW(w).nrects) ;
	}
}

/*ARGSUSED*/
static
ac_select(w,event)
	Widget w ;
	XButtonEvent *event ;
{
	int ln = find_y_pos(w,event->y) ;
	int pos = find_x_pos(w,ln,event->x,0) ;
	switch(VTW(w).n_select)
	{
		case 1 :
			if (pos == VTW(w).last_pos && event->time < VTW(w).last_time + 300)
			{
				int lpos = VTW(w).lines[ln].pos ;
				int lend = lpos + VTW(w).lines[ln].len - 1 ;
				if (pos >= lpos && pos <= lend && (
				   VTW(w).str[pos] == '\t' ||
				   VTW(w).str[pos] == ' '))
				{
					place_cursor(w,pos) ;
				}
				else
				{
					int i ;
					int j ;
					for(i = pos; i >= lpos && VTW(w).str[i] != ' ' && VTW(w).str[i] != '\t'; i--) ;
					for(j = pos; j <= lend && VTW(w).str[j] != ' ' && VTW(w).str[j] != '\t'; j++) ;
					highlight_text(w,i + 1, j - i - 1) ;
					own_selection(w) ;
				}
				VTW(w).n_select = 2 ;
				return ;
			}
			break ;
		case 2 :
			if (pos == VTW(w).last_pos && event->time < VTW(w).last_time + 600)
			{
				int lpos = VTW(w).lines[ln].pos ;
				int len = VTW(w).lines[ln].len ;
				highlight_text(w,lpos,len) ;
				own_selection(w) ;
				VTW(w).n_select = 3 ;
				return ;
			}
			break ;
		case 3 :
			if (pos == VTW(w).last_pos && event->time < VTW(w).last_time + 900)
			{
				highlight_text(w,0,strlen(VTW(w).str) + 1) ;
				own_selection(w) ;
				VTW(w).n_select = 0 ;
				return ;
			}
			break ;
	}
	{
		int hb = VTW(w).highlight_begin ;
		int he = hb + VTW(w).highlight_length - 1 ;
		if ((pos >= hb && pos <= he) && VTW(w).drag)
		{
			int l1 = find_line(w,hb) ;
			int l2 = find_line(w,he) ;
			int nrects = l2 - l1 + 1 ;
			XRectangle *rects = (XRectangle *)malloc(nrects * sizeof(XRectangle)) ;
			if (nrects == 1)
			{
				int x = text_width(w,VTW(w).str + VTW(w).lines[l1].pos,hb - VTW(w).lines[l1].pos,0) ;
				rects[0].x = 0 ;
				rects[0].y = 0 ;
				rects[0].width = text_width(w,VTW(w).str + hb,he - hb + 1,x) ;
				rects[0].height = VTW(w).fhgt ;
			}
			else
			{
				int i ;
				int dx ;
				{
					int lt = hb - VTW(w).lines[l1].pos ;
					dx = text_width(w,VTW(w).str + VTW(w).lines[l1].pos,lt,0) ;
					rects[0].x = 0 ;
					rects[0].y = 0 ;
					rects[0].width = text_width(w,VTW(w).str + hb,VTW(w).lines[l1].len - lt,dx) ;
					rects[0].height = VTW(w).fhgt ;
				}
				for(i = l1 + 1; i < l2; i++)
				{
					rects[i - l1].x = -dx ;
					rects[i - l1].y = (i - l1) * VTW(w).fhgt ;
					rects[i - l1].width = text_width(w,VTW(w).str + VTW(w).lines[i].pos,VTW(w).lines[i].len,0) ;
					rects[i - l1].height = VTW(w).fhgt ;
				}
				{
					char *s = VTW(w).str + VTW(w).lines[i].pos ;
					int sl = he - VTW(w).lines[i].pos + 1 ;
					rects[i - l1].x = -dx ;
					rects[i - l1].y = (i - l1) * VTW(w).fhgt ;
					rects[i - l1].width = text_width(w,s,sl,0) ;
					rects[i - l1].height = VTW(w).fhgt ;
				}
			}
			VTW(w).nrects = nrects ;
			VTW(w).rects = rects ;
			VTW(w).is_dragging = TRUE ;
			VTW(w).has_drawn_rects = FALSE ;
			VTW(w).n_select = 0 ;
			return ;
		}
	}
	VTW(w).last_pos = pos ;
	VTW(w).ext_start = pos ;
	VTW(w).is_extending = True ;
	VTW(w).last_time = event->time ;
	place_cursor(w,pos) ;
	disown_selection(w) ;
	VTW(w).n_select = 1 ;
}

static
Window
find_window(dpy,x,y,x1,y1)
	Display *dpy ;
	int *x1 ;
	int *y1 ;
{
	Window root = DefaultRootWindow(dpy) ;
	Window child = root ;
	Window w = root ;
	int nx,ny ;
	*x1 = x ;
	*y1 = y ;
	while(XTranslateCoordinates(dpy,root,w,x,y,&nx,&ny,&child) && child != None)
	{
		w = child ;
		*x1 = nx ;
		*y1 = ny ;
	}
	return w ;
}

/*ARGSUSED*/
static
ac_unselect(w,event)
	Widget w ;
	XButtonEvent *event ;
{
	if (VTW(w).is_dragging)
	{
		if (VTW(w).has_drawn_rects && VTW(w).drag)
		{
			XClientMessageEvent evt ;
			int x,y ;
			draw_lines(w) ;
			evt.type = ClientMessage ;
			evt.display = XtDisplay(w) ;
			evt.window = find_window(XtDisplay(w),event->x_root,event->y_root,&x,&y) ;
			evt.message_type = XInternAtom(XtDisplay(w),"VNS_DRAG_DROP",False) ;
			evt.format = 32 ;
			evt.data.l[0] = event->time ;
			evt.data.l[1] = x ;
			evt.data.l[2] = y ;
			evt.data.l[3] = evt.window ;
			evt.data.l[4] = XInternAtom(XtDisplay(w),"NOTEBOOK_DROP_TEXT",False);
			XSendEvent(XtDisplay(w),evt.window,True,0L,&evt) ;
		}
		else
		{
			place_cursor(w,find_xy_pos(w,event->x,event->y,0)) ;
			disown_selection(w) ;
			VTW(w).n_select = 0 ;
		}
		VTW(w).is_dragging = FALSE ;
		return ;
	}
	if (VTW(w).is_extending && VTW(w).highlight_length)
	{
		own_selection(w) ;
	}
	VTW(w).is_extending = False ;
}

/*ARGSUSED*/
static
ac_extend(w,event)
	Widget w ;
	XMotionEvent *event ;
{
	if (VTW(w).is_extending)
	{
		int pos = find_xy_pos(w,event->x,event->y,0) ;
		int diff = pos - VTW(w).ext_start ;
		VTW(w).n_select = 0 ;
		if (diff > 0)
		{
			int hb = VTW(w).ext_start ;
			int hl = diff + 1 ;
			highlight_text(w,hb,hl) ;
		}
		else if (!diff)
		{
			place_cursor(w,pos) ;
		}
		else
		{
			int hb = pos ;
			int hl = -diff ;
			highlight_text(w,hb,hl) ;
		}
	}
	else if (VTW(w).is_dragging)
	{
		if (VTW(w).has_drawn_rects)
		{
			draw_lines(w) ;
			add_val_to_rects(w,-VTW(w).last_drag_x,-VTW(w).last_drag_y) ;
		}
		VTW(w).last_drag_x = event->x_root ;
		VTW(w).last_drag_y = event->y_root ;
		add_val_to_rects(w,VTW(w).last_drag_x,VTW(w).last_drag_y) ;
		draw_lines(w) ;
		VTW(w).has_drawn_rects = TRUE ;
	}
}

static
int
find_word_beg(s,i)
	char *s ;
{
	while(i >= 0 && s[i] != ' ' && s[i] != '\n' && s[i] != ' ')
	{
		i-- ;
	}
	return i + 1 ;
}

static
add_string_to_text(w,s)
	Widget w ;
	char *s ;
{
	int beg = VTW(w).highlight_begin ;
	int hl = VTW(w).highlight_length ;
	add_str_to_txt(w,s,beg,hl) ;
	place_cursor(w,beg + 1) ;
	disown_selection(w) ;
}

static
add_str_to_txt(w,s,beg,hl)
	Widget w ;
	char *s ;
{
	if (beg + hl > strlen(VTW(w).str))
	{
		hl = strlen(VTW(w).str) - beg ;
	}
	{
		int sl = strlen(s);
		if (sl == 0)
		{
			char *new;
			char *l;
			int i;
			new = malloc(hl + 1);
			l = new;
			for(i = beg; i < beg + hl; i++)
			{
				*l++ = VTW(w).str[i];
			}
			*l = 0;
			free(VTW(w).cutbuf);
			VTW(w).cutbuf = new;
			own_buffer(w);
		}
	}
	{
	Line *lines = VTW(w).lines ;
	int nlines = VTW(w).nlines ;
	int diff = strlen(s) - hl ;
	char *new_s = malloc(strlen(VTW(w).str) + diff + 1) ;
	char *l = new_s ;
	int i ;
	int lp ;
	int startw = find_word_beg(VTW(w).str,beg - 1) - 1 ;
	int st;
	if (startw < 0)
	{
		startw = 0 ;
	}
	for(i = 0; i < beg; *l++ = VTW(w).str[i++]) ;
	for(i = 0; s[i]; i++)
	{
		int c = s[i] ;
		if (c == '\r')
		{
			c = '\n' ;
		}
		*l++ = c ;
	}
	for(i = beg + hl; VTW(w).str[i]; *l++ = VTW(w).str[i++]) ;
	*l = 0 ;

	/* erase the old cursor */
	erase_cursor(w) ;

	/* free the old string */
	free(VTW(w).str) ;
	VTW(w).str = new_s ;
	VTW(w).lines = NULL ;
	VTW(w).mlines = 0 ;

	st = set_up_offsets(w) ;
	lp = find_line(w,startw) ;

	/* Draw character immediately before */
	redisplay_line_portion(w,lp,new_s + startw,
		new_s + VTW(w).lines[lp].pos + VTW(w).lines[lp].len - 1) ;
	lp++ ;
	while(lp < nlines && lp < VTW(w).nlines)
	{
		if (lines[lp].pos + diff == VTW(w).lines[lp].pos)
		{
			goto same ;
		}
		redisplay_line(w,lp++) ;
	}
	while(lp < VTW(w).nlines)
	{
		redisplay_line(w,lp++) ;
	}
	if (lp < nlines)
	{
		redisplay_eof(w) ;
	}
	same :
	free((char *)lines) ;
	
	/* if set_up_offsets returned True, then redisplay entire window */
	if (st == True)
	{
		redisplay_window(w);
	}
	else
	{
		/* redisplay the cursor */
		draw_cursor(w) ;
	}
	}
}

/*
 * insert a character
 */
/*ARGSUSED*/
static
ac_insert_char(w,event)
	Widget w ;
	XKeyEvent *event ;
{
	if (!VTW(w).read_only)
	{
		static char buf[80] ;
		KeySym key ;
		XComposeStatus compose ;
		int bc = XLookupString(event,buf,sizeof buf,&key,&compose) ;
		if (bc > 0)
		{
			buf[bc] = 0 ;
			add_string_to_text(w,buf) ;
		}
	}
}

/*ARGSUSED*/
static
ac_delete_char(w)
	Widget w ;
{
	if (!VTW(w).read_only)
	{
		int pos = VTW(w).highlight_begin ;
		int hl = VTW(w).highlight_length ;
		if (hl)
		{
			place_cursor(w,pos) ;
			disown_selection(w) ;
			add_str_to_txt(w,"",pos,hl) ;
		}
		else if (VTW(w).highlight_begin > 0)
		{
			place_cursor(w,pos - 1) ;
			disown_selection(w) ;
			add_str_to_txt(w,"",pos - 1,1) ;
		}
	}
}

/*ARGSUSED*/
static
ac_backward_char(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	if (pos > 0)
	{
		place_cursor(w,pos - 1) ;
		disown_selection(w) ;
	}
}

/*ARGSUSED*/
static
ac_forward_char(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	if (pos < strlen(VTW(w).str))
	{
		place_cursor(w,pos + 1) ;
		disown_selection(w) ;
	}
}

/*
 * move down a line from the current cursor postion
 */
/*ARGSUSED*/
static
ac_down_line(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	int line = find_line(w,pos) ;

	/* check to see if we should scroll or not */
	if (line < VTW(w).nlines - 1)
	{
		int y;
		y = w->core.height - 1;
		if (line + 1 > find_y_pos(w,y))
		{
			int sval;

			++VTW(w).top_row;
			redisplay_window(w);

			/* update vertical bar value */
			XtVaGetValues(VTW(w).vbar,
				XmNvalue,&sval,
				NULL);
			++sval;
			XtVaSetValues(VTW(w).vbar,
				XmNvalue,sval,
				NULL);
		}
	}

	/* calculate and place cursor on next line */
	if (line < VTW(w).nlines - 1)
	{
		int ll = pos - VTW(w).lines[line].pos + 1 ;
		if (ll > VTW(w).lines[line + 1].len)
		{
			ll = VTW(w).lines[line + 1].len ;
		}
		place_cursor(w,VTW(w).lines[line + 1].pos + ll - 1) ;
		disown_selection(w) ;
	}
}

/*
 * Move up a line from the current cursor position.
 */
/*ARGSUSED*/
static
ac_up_line(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	int line = find_line(w,pos) ;

	/* check to see if we should scroll or not */
	if (line > 0)
	{
		int y;
		y = w->core.height - 1;
		if (line == VTW(w).top_row)
		{
			int sval;

			if (VTW(w).top_row > 0) --VTW(w).top_row;
			redisplay_window(w);

			/* update vertical bar value */
			XtVaGetValues(VTW(w).vbar,
				XmNvalue,&sval,
				NULL);
			if (sval > 0) --sval;
			XtVaSetValues(VTW(w).vbar,
				XmNvalue,sval,
				NULL);
		}
	}

	if (line > 0)
	{
		int ll = pos - VTW(w).lines[line].pos + 1 ;
		if (ll > VTW(w).lines[line - 1].len)
		{
			ll = VTW(w).lines[line - 1].len ;
		}
		place_cursor(w,VTW(w).lines[line - 1].pos + ll - 1) ;
		disown_selection(w) ;
	}
}

/*ARGSUSED*/
static
ac_start_of_line(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	int ln = find_line(w,pos) ;
	place_cursor(w,VTW(w).lines[ln].pos) ;
	disown_selection(w) ;
}

/*ARGSUSED*/
static
ac_end_of_line(w)
	Widget w ;
{
	int pos = VTW(w).highlight_begin ;
	int ln = find_line(w,pos) ;
	place_cursor(w,VTW(w).lines[ln].pos + VTW(w).lines[ln].len - 1) ;
	disown_selection(w) ;
}

/*ARGSUSED*/
static
ac_kill_end_of_line(w)
	Widget w ;
{
	if (!VTW(w).read_only)
	{
		int pos = VTW(w).highlight_begin ;
		int ln = find_line(w,pos) ;
		place_cursor(w,pos) ;
		disown_selection(w) ;
		add_str_to_txt(w,"",pos,VTW(w).lines[ln].len - (pos - VTW(w).lines[ln].pos)) ;
	}
}

/*ARGSUSED*/
static
ac_erase_line(w)
	Widget w ;
{
	if (!VTW(w).read_only)
	{
		int pos = VTW(w).highlight_begin ;
		int ln = find_line(w,pos) ;
		place_cursor(w,VTW(w).lines[ln].pos) ;
		disown_selection(w) ;
		add_str_to_txt(w,"",VTW(w).lines[ln].pos,VTW(w).lines[ln].len) ;
	}
}

/*ARGSUSED*/
static
ac_focus_in(w,event)
	Widget w ;
	XFocusChangeEvent *event ;
{
	if (event->detail == NotifyPointer)
	{
		return ;
	}
	if (!VTW(w).have_focus)
	{
		VTW(w).have_focus = True ;
		XSetFillStyle(XtDisplay(w),VTW(w).curs_gc,FillSolid) ;
		draw_cursor(w) ;
	}
}

/*ARGSUSED*/
static
ac_focus_out(w,event)
	Widget w ;
	XFocusChangeEvent *event ;
{
	if (event->detail == NotifyPointer)
	{
		return ;
	}
	VTW(w).have_focus = False ;
	XSetFillStyle(XtDisplay(w),VTW(w).curs_gc,FillStippled) ;
	erase_cursor(w) ;
	draw_cursor(w) ;
}

/*ARGSUSED*/
static
ac_message(w,event)
	Widget w ;
	XClientMessageEvent *event ;
{
	if (!VTW(w).read_only && 
		event->message_type == XInternAtom(XtDisplay(w),"VNS_DRAG_DROP",False))
	{
		if (event->data.l[4] == XInternAtom(XtDisplay(w),"NOTEBOOK_DROP_TEXT",False))
		{
			VTW(w).use_buffer = FALSE;
			read_selection(w,VTW(w).highlight_begin) ;
		}
	}
}

static
ac_cut(w)
	Widget w ;
{
	if (!VTW(w).read_only)
	{
		int pos = VTW(w).highlight_begin ;
		int hl = VTW(w).highlight_length ;
		if (hl)
		{
			place_cursor(w,pos) ;
			disown_selection(w) ;
			add_str_to_txt(w,"",pos,hl) ;
		}
	}
}

static
ac_copy(w)
	Widget w ;
{
	int beg = VTW(w).highlight_begin;
	int hl = VTW(w).highlight_length;
	if (hl)
	{
		char *new;
		char *l;
		int i;
		new = malloc(hl + 1);
		l = new;
		for(i = beg; i < beg + hl; i++)
		{
			*l++ = VTW(w).str[i];
		}
		*l = 0;
		free(VTW(w).cutbuf);
		VTW(w).cutbuf = new;
		own_buffer(w);
	}
}

static
ac_paste(w)
	Widget w ;
{
	if (!VTW(w).read_only)
	{
		read_selection(w,VTW(w).highlight_begin) ;
	}
}

static
ac_redraw(w)
	Widget w ;
{
	XClearArea(XtDisplay(w),XtWindow(w),0,0,0,0,True) ;
}

VnsHighlightText(w,hb,hl)
	Widget w ;
{
	int l = strlen(VTW(w).str) + 1 ;
	if (hb < 0)
	{
		hb = 0 ;
	}
	if (hb > l)
	{
		hb = l ;
	}
	if (hb + hl > l)
	{
		hl = l - hb ;
	}
	highlight_text(w,hb,hl) ;
	own_selection(w) ;
}

/*ARGSUSED*/
static
GotSecondary(w,data,selection,type,value,length,format)
	Widget w ;
	XtPointer data ;
	Atom *selection ;
	Atom *type ;
	XtPointer value ;
	unsigned long *length ;
	int *format ;
{
	/* if there is text selected then add text */
	if (*type == XA_STRING)
	{
/*
		value[*length] - 0 ;
*/
		add_str_to_txt(w,value,VTW(w).sel_pos,0) ;
		free(value) ;
	}
}

/*ARGSUSED*/
static
GotSelection(w,data,selection,type,value,length,format)
	Widget w ;
	XtPointer data ;
	Atom *selection ;
	Atom *type ;
	XtPointer value ;
	unsigned long *length ;
	int *format ;
{
	/* if there is text selected then add text */
	if (*type == XA_STRING)
	{
		value[*length] - 0 ;
		add_str_to_txt(w,value,VTW(w).sel_pos,0) ;
		free(value) ;
	}
	else
	{
		XtGetSelectionValue(w,XA_SECONDARY,XA_STRING,
			GotSecondary,(XtPointer)NULL,CurrentTime) ;
	}
}

static
read_selection(w,pos)
	Widget w ;
{
	VTW(w).sel_pos = pos ;
	XtGetSelectionValue(w,XA_PRIMARY,XA_STRING,
		GotSelection,(XtPointer)NULL,CurrentTime) ;
}

static
add_val_to_rects(w,val_x,val_y)
	Widget w ;
{
	int i ;
	for(i = 0; i < VTW(w).nrects; i++)
	{
		VTW(w).rects[i].x += val_x ;
		VTW(w).rects[i].y += val_y ;
	}
}

/*
 *	User Functions
 */

VnsAddText(w,s)
	Widget w ;
	char *s ;
{
	add_string_to_text(w,s) ;
}

VnsTextPlaceCursor(w)
	Widget w;
{
	ac_end_of_line(w);
}
