/*-
 * Copyright (c) 1996
 *	Rob Zimmermann.  All rights reserved.
 * Copyright (c) 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#ifndef lint
static const char sccsid[] = "$Id: m_vi.c,v 8.41 2003/11/05 17:10:01 skimo Exp $ (Berkeley) $Date: 2003/11/05 17:10:01 $";
#endif /* not lint */

#include <sys/types.h>
#include <sys/queue.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include <Xm/PanedW.h>
#include <Xm/DrawingA.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/ScrollBar.h>

#include <bitstring.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#undef LOCK_SUCCESS
#include "../common/common.h"
#include "../ipc/ip.h"
#include "m_motif.h"
#include "vi_mextern.h"
#include "pathnames.h"

extern int vi_ofd;

static	void	f_copy(String *buffer, int *len);
static	void	f_paste(int widget, int buffer, int length);
static	void	f_clear(Widget widget);


/*
 * Globals and costants
 */

#define	BufferSize	1024

static	XFontStruct	*font;
static	GC		gc;
	GC		__vi_copy_gc;
static	XtAppContext	ctx;

	xvi_screen	*__vi_screen = NULL;
static	Cursor		std_cursor;
static	Cursor		busy_cursor;
static	XtTranslations	area_trans;
static	int		multi_click_length;

void (*__vi_exitp)();				/* Exit function. */


/* hack for drag scrolling...
 * I'm not sure why, but the current protocol gets out of sync when
 * a lot of drag messages get passed around.  Likely, we need to wait
 * for core to finish repainting the screen before sending more drag
 * messages.
 * To that end, we set scroll_block when we receive input from the scrollbar,
 * and we clear it when we process the IPO_REFRESH message from core.
 * A specific SCROLL_COMPLETED message would be better, but this seems to work.
 */

static Boolean scroll_block = False;

/*
 * PUBLIC: void __vi_set_scroll_block __P((void));
 */
void
__vi_set_scroll_block(void)
{
	scroll_block = True;
}

/*
 * PUBLIC: void __vi_clear_scroll_block __P((void));
 */
void
__vi_clear_scroll_block(void)
{
	scroll_block = False;
}


#if defined(__STDC__)
static	void	set_gc_colors( xvi_screen *this_screen, int val )
#else
static	void	set_gc_colors( this_screen, val )
xvi_screen	*this_screen;
int		val;
#endif
{
    static Pixel	fg, bg, hi, shade;
    static int		prev = COLOR_INVALID;

    /* no change? */
    if ( prev == val ) return;

    /* init? */
    if ( gc == NULL ) {

	/* what colors are selected for the drawing area? */
	XtVaGetValues( this_screen->area,
		       XtNbackground,		&bg,
		       XtNforeground,		&fg,
		       XmNhighlightColor,	&hi,
		       XmNtopShadowColor,	&shade,
		       0
		       );

	gc = XCreateGC( XtDisplay(this_screen->area),
		        DefaultRootWindow(XtDisplay(this_screen->area)),
			0,
			0
			);

	XSetFont( XtDisplay(this_screen->area), gc, font->fid );
    }

    /* special colors? */
    if ( val & COLOR_CARET ) {
	XSetForeground( XtDisplay(this_screen->area), gc, fg );
	XSetBackground( XtDisplay(this_screen->area), gc, hi );
    }
    else if ( val & COLOR_SELECT ) {
	XSetForeground( XtDisplay(this_screen->area), gc, fg );
	XSetBackground( XtDisplay(this_screen->area), gc, shade );
    }
    else switch (val) {
	case COLOR_STANDARD:
	    XSetForeground( XtDisplay(this_screen->area), gc, fg );
	    XSetBackground( XtDisplay(this_screen->area), gc, bg );
	    break;
	case COLOR_INVERSE:
	    XSetForeground( XtDisplay(this_screen->area), gc, bg );
	    XSetBackground( XtDisplay(this_screen->area), gc, fg );
	    break;
	default:	/* implement color map later */
	    break;
    }
}


/*
 * Memory utilities
 */

#ifdef REALLOC
#undef REALLOC
#endif

#define REALLOC( ptr, size )	\
	((ptr == NULL) ? malloc(size) : realloc(ptr,size))


/* X windows routines.
 * We currently create a single, top-level shell.  In that is a
 * single drawing area into which we will draw text.  This allows
 * us to put multi-color (and font, but we'll never build that) text
 * into the drawing area.  In the future, we'll add scrollbars to the
 * drawing areas
 */

void	select_start();
void	select_extend();
void	select_paste();
void	key_press();
void	insert_string();
void	beep __P((Widget w));
void	find();
void	command();

static XtActionsRec	area_actions[] = {
    { "select_start",	select_start	},
    { "select_extend",	select_extend	},
    { "select_paste",	select_paste	},
    { "key_press",	key_press	},
    { "insert_string",	insert_string	},
    { "beep",		beep		},
    { "find",		find		},
    { "command",	command		},
};

char	areaTrans[] =
    "<Btn1Down>:	select_start()		\n\
     <Btn1Motion>:	select_extend()		\n\
     <Btn2Down>:	select_paste()		\n\
     <Btn3Down>:	select_extend()		\n\
     <Btn3Motion>:	select_extend()		\n\
     <Key>End:		command(VI_C_BOTTOM)	\n\
     <Key>Escape:	command(EINSERT)	\n\
     <Key>Find:		find()			\n\
     <Key>Home:		command(VI_C_TOP)	\n\
     <Key>Next:		command(VI_C_PGDOWN)	\n\
     <Key>Prior:	command(VI_C_PGUP)	\n\
     <Key>osfBackSpace:	command(VI_C_LEFT)	\n\
     <Key>osfBeginLine:	command(VI_C_BOL)	\n\
     <Key>osfCopy:	beep()			\n\
     <Key>osfCut:	beep()			\n\
     <Key>osfDelete:	command(VI_C_DEL)	\n\
     <Key>osfDown:	command(VI_C_DOWN)	\n\
     <Key>osfEndLine:	command(VI_C_EOL)	\n\
     <Key>osfInsert:	command(VI_C_INSERT)	\n\
     <Key>osfLeft:	command(VI_C_LEFT)	\n\
     <Key>osfPageDown:	command(VI_C_PGDOWN)	\n\
     <Key>osfPageUp:	command(VI_C_PGUP)	\n\
     <Key>osfPaste:	insert_string(p)	\n\
     <Key>osfRight:	command(VI_C_RIGHT)	\n\
     <Key>osfUndo:	command(VI_UNDO)	\n\
     <Key>osfUp:	command(VI_C_UP)	\n\
     Ctrl<Key>C:	command(VI_INTERRUPT)	\n\
     <Key>:		key_press()";


static  XutResource resource[] = {
    { "font",		XutRKfont,	&font		},
    { "pointerShape",	XutRKcursor,	&std_cursor	},
    { "busyShape",	XutRKcursor,	&busy_cursor	},
};


/*
 * vi_input_func --
 *	We've received input on the pipe from vi.
 *
 * PUBLIC: void vi_input_func __P((XtPointer, int *, XtInputId *));
 */
void
vi_input_func(XtPointer client_data, int *source, XtInputId *id)
{
	/* Parse and dispatch on commands in the queue. */
	(void)ipvi_motif->input(ipvi_motif, *source);

#ifdef notdef
	/* Check the pipe for unused events when not busy. */
	XtAppAddWorkProc(ctx, process_pipe_input, NULL);
#endif
}



/* Send the window size. */
#if defined(__STDC__)
static	void	send_resize( xvi_screen *this_screen )
#else
static	void	send_resize( this_screen )
xvi_screen	*this_screen;
#endif
{
    IP_BUF	ipb;

    ipb.val1 = this_screen->rows;
    ipb.val2 = this_screen->cols;
    ipb.code = VI_RESIZE;

#ifdef TRACE
    vtrace("resize_func ( %d x %d )\n", this_screen->rows, this_screen->cols);
#endif

    /* send up the pipe */
    vi_send(vi_ofd, "12", &ipb);
}


#if defined(__STDC__)
static	void	resize_backing_store( xvi_screen *this_screen )
#else
static	void	resize_backing_store( this_screen )
xvi_screen	*this_screen;
#endif
{
    int	total_chars = this_screen->rows * this_screen->cols;

    this_screen->characters	= REALLOC( this_screen->characters,
					   total_chars
					   );
    memset( this_screen->characters, ' ', total_chars );

    this_screen->flags		= REALLOC( this_screen->flags,
					   total_chars
					   );
    memset( this_screen->flags, 0, total_chars );
}



/* X will call this when we are resized */
#if defined(__STDC__)
static	void	resize_func( Widget wid,
			     XtPointer client_data,
			     XtPointer call_data
			     )
#else
static	void	resize_func( wid, client_data, call_data )
Widget		wid;
XtPointer	client_data;
XtPointer	call_data;
#endif
{
    xvi_screen			*this_screen = (xvi_screen *) client_data;
    Dimension			height, width;

    XtVaGetValues( wid, XmNheight, &height, XmNwidth, &width, 0 );

    /* generate correct sizes when we have font metrics implemented */
    this_screen->cols = width / this_screen->ch_width;
    this_screen->rows = height / this_screen->ch_height;

    resize_backing_store( this_screen );
    send_resize( this_screen );
}


/*
 * __vi_draw_text --
 *	Draw from backing store.
 *
 * PUBLIC: void	__vi_draw_text __P((xvi_screen *, int, int, int));
 */
void
__vi_draw_text(xvi_screen *this_screen, int row, int start_col, int len)
{
    int		col, color, xpos;
    char	*start, *end;

    start = CharAt( __vi_screen, row, start_col );
    color = *FlagAt( __vi_screen, row, start_col );
    xpos  = XPOS( __vi_screen, start_col );

    /* one column at a time */
    for ( col=start_col;
	  col<this_screen->cols && col<start_col+len;
	  col++ ) {

	/* has the color changed? */
	if ( *FlagAt( __vi_screen, row, col ) == color )
	    continue;

	/* is there anything to write? */
	end  = CharAt( __vi_screen, row, col );
	if ( end == start )
	    continue;

	/* yes. write in the previous color */
	set_gc_colors( __vi_screen, color );

	/* add to display */
	XDrawImageString( XtDisplay(__vi_screen->area),
			  XtWindow(__vi_screen->area),
			  gc,
			  xpos,
			  YPOS( __vi_screen, row ),
			  start,
			  end - start
			  );

	/* this is the new context */
	color = *FlagAt( __vi_screen, row, col );
	xpos  = XPOS( __vi_screen, col );
	start = end;
    }

    /* is there anything to write? */
    end = CharAt( __vi_screen, row, col );
    if ( end != start ) {
	/* yes. write in the previous color */
	set_gc_colors( __vi_screen, color );

	/* add to display */
	XDrawImageString( XtDisplay(__vi_screen->area),
			  XtWindow(__vi_screen->area),
			  gc,
			  xpos,
			  YPOS( __vi_screen, row ),
			  start,
			  end - start
			  );
    }
}


/* set clipping rectangles accordingly */
#if defined(__STDC__)
static	void	add_to_clip( xvi_screen *cur_screen, int x, int y, int width, int height )
#else
static	void	add_to_clip( cur_screen, x, y, width, height )
	xvi_screen *cur_screen;
	int	x;
	int	y;
	int	width;
	int	height;
#endif
{
    XRectangle	rect;
    rect.x	= x;
    rect.y	= y;
    rect.height	= height;
    rect.width	= width;
    if ( cur_screen->clip == NULL )
	cur_screen->clip = XCreateRegion();
    XUnionRectWithRegion( &rect, cur_screen->clip, cur_screen->clip );
}


/*
 * __vi_expose_func --
 *	Redraw the window's contents.
 *
 * NOTE: When vi wants to force a redraw, we are called with NULL widget
 *	 and call_data.
 *
 * PUBLIC: void	__vi_expose_func __P((Widget, XtPointer, XtPointer));
 */
void
__vi_expose_func(Widget wid, XtPointer client_data, XtPointer call_data)
{
    xvi_screen			*this_screen;
    XmDrawingAreaCallbackStruct	*cbs;
    XExposeEvent		*xev;
    XGraphicsExposeEvent	*gev;
    int				row;

    /* convert pointers */
    this_screen = (xvi_screen *) client_data;
    cbs		= (XmDrawingAreaCallbackStruct *) call_data;

    /* first exposure? tell vi we are ready... */
    if ( this_screen->init == False ) {

	/* what does the user want to see? */
	__vi_set_cursor( __vi_screen, False );

	/* vi wants a resize as the first event */
	send_resize( __vi_screen );

	/* fine for now.  we'll be back */
	this_screen->init = True;
	return;
    }

    if ( call_data == NULL ) {

	/* vi core calls this when it wants a full refresh */
#ifdef TRACE
	vtrace("expose_func:  full refresh\n");
#endif

	XClearWindow( XtDisplay(this_screen->area),
		      XtWindow(this_screen->area)
		      );
    }
    else {
	switch ( cbs->event->type ) {

	    case GraphicsExpose:
		gev = (XGraphicsExposeEvent *) cbs->event;

		/* set clipping rectangles accordingly */
		add_to_clip( this_screen,
			     gev->x, gev->y,
			     gev->width, gev->height
			     );

		/* X calls here when XCopyArea exposes new bits */
#ifdef TRACE
		vtrace("expose_func (X):  (x=%d,y=%d,w=%d,h=%d), count=%d\n",
			     gev->x, gev->y,
			     gev->width, gev->height,
			     gev->count);
#endif

		/* more coming?  do it then */
		if ( gev->count > 0 ) return;

		/* set clipping region */
		XSetRegion( XtDisplay(wid), gc, this_screen->clip );
		break;

	    case Expose:
		xev = (XExposeEvent *) cbs->event;

		/* set clipping rectangles accordingly */
		add_to_clip( this_screen,
			     xev->x, xev->y,
			     xev->width, xev->height
			     );

		/* Motif calls here when DrawingArea is exposed */
#ifdef TRACE
		vtrace("expose_func (Motif): (x=%d,y=%d,w=%d,h=%d), count=%d\n",
			     xev->x, xev->y,
			     xev->width, xev->height,
			     xev->count);
#endif

		/* more coming?  do it then */
		if ( xev->count > 0 ) return;

		/* set clipping region */
		XSetRegion( XtDisplay(wid), gc, this_screen->clip );
		break;

	    default:
		/* don't care? */
		return;
	}
    }

    /* one row at a time */
    for (row=0; row<this_screen->rows; row++) {

	/* draw from the backing store */
	__vi_draw_text( this_screen, row, 0, this_screen->cols );
    }

    /* clear clipping region */
    XSetClipMask( XtDisplay(this_screen->area), gc, None );
    if ( this_screen->clip != NULL ) {
	XDestroyRegion( this_screen->clip );
	this_screen->clip = NULL;
    }

}


#if defined(__STDC__)
static void	xexpose	( Widget w,
			  XtPointer client_data,
			  XEvent *ev,
			  Boolean *cont
			  )
#else
static void	xexpose	( w, client_data, ev, cont )
Widget		w;
XtPointer	client_data;
XEvent		*ev;
Boolean		*cont;
#endif
{
    XmDrawingAreaCallbackStruct	cbs;

    switch ( ev->type ) {
	case GraphicsExpose:
	    cbs.event	= ev;
	    cbs.window	= XtWindow(w);
	    cbs.reason	= XmCR_EXPOSE;
	    __vi_expose_func( w, client_data, (XtPointer) &cbs );
	    *cont	= False;	/* we took care of it */
	    break;
	default:
	    /* don't care */
	    break;
    }
}


/* unimplemented keystroke or command */
#if defined(__STDC__)
static void	beep( Widget w )
#else
static void	beep( w )
Widget	w;
#endif
{
    XBell(XtDisplay(w),0);
}


/* give me a search dialog */
#if defined(__STDC__)
static void	find( Widget w )
#else
static void	find( w )
Widget	w;
#endif
{
    __vi_show_search_dialog( w, "Find" );
}

/*
 * command --
 *	Translate simple keyboard input into vi protocol commands.
 */
static	void
command(Widget widget, XKeyEvent *event, String *str, Cardinal *cardinal)
{
	static struct {
		String	name;
		int	code;
		int	count;
	} table[] = {
		{ "VI_C_BOL",		VI_C_BOL,	0 },
		{ "VI_C_BOTTOM",	VI_C_BOTTOM,	0 },
		{ "VI_C_DEL",		VI_C_DEL,	0 },
		{ "VI_C_DOWN",		VI_C_DOWN,	1 },
		{ "VI_C_EOL",		VI_C_EOL,	0 },
		{ "VI_C_INSERT",	VI_C_INSERT,	0 },
		{ "VI_C_LEFT",		VI_C_LEFT,	0 },
		{ "VI_C_PGDOWN",	VI_C_PGDOWN,	1 },
		{ "VI_C_PGUP",		VI_C_PGUP,	1 },
		{ "VI_C_RIGHT",		VI_C_RIGHT,	0 },
		{ "VI_C_TOP",		VI_C_TOP,	0 },
		{ "VI_C_UP",		VI_C_UP,	1 },
		{ "VI_INTERRUPT",	VI_INTERRUPT,	0 },
	};
	IP_BUF ipb;
	int i;

	/*
	 * XXX
	 * Do fast lookup based on character #6 -- sleazy, but I don't
	 * want to do 10 strcmp's per keystroke.
	 */
	ipb.val1 = 1;
	for (i = 0; i < XtNumber(table); i++)
		if (table[i].name[6] == (*str)[6] &&
		    strcmp(table[i].name, *str) == 0) {
			ipb.code = table[i].code;
			vi_send(vi_ofd, table[i].count ? "1" : NULL, &ipb);
			return;
		}

	/* oops. */
	beep(widget);
}

/* mouse or keyboard input. */
#if defined(__STDC__)
static	void	insert_string( Widget widget, 
			       XKeyEvent *event, 
			       String *str, 
			       Cardinal *cardinal
			       )
#else
static	void	insert_string( widget, event, str, cardinal )
Widget          widget; 
XKeyEvent       *event; 
String          *str;  
Cardinal        *cardinal;
#endif
{
    IP_BUF	ipb;

    ipb.len1 = strlen( *str );
    if ( ipb.len1 != 0 ) {
	ipb.code = VI_STRING;
	ipb.str1 = *str;
	vi_send(vi_ofd, "a", &ipb);
    }

#ifdef TRACE
    vtrace("insert_string {%.*s}\n", strlen( *str ), *str );
#endif
}


/* mouse or keyboard input. */
#if defined(__STDC__)
static	void	key_press( Widget widget, 
			   XKeyEvent *event, 
			   String str, 
			   Cardinal *cardinal
			   )
#else
static	void	key_press( widget, event, str, cardinal )
Widget          widget; 
XKeyEvent       *event; 
String          str;  
Cardinal        *cardinal;
#endif
{
    IP_BUF	ipb;
    char	bp[BufferSize];

    ipb.len1 = XLookupString( event, bp, BufferSize, NULL, NULL );
    if ( ipb.len1 != 0 ) {
	ipb.code = VI_STRING;
	ipb.str1 = bp;
#ifdef TRACE
	vtrace("key_press {%.*s}\n", ipb.len1, bp );
#endif
	vi_send(vi_ofd, "a", &ipb);
    }

}


#if defined(__STDC__)
static	void	scrollbar_moved( Widget widget,
				 XtPointer ptr,
				 XmScrollBarCallbackStruct *cbs
				 )
#else
static	void				scrollbar_moved( widget, ptr, cbs )
	Widget				widget;
	XtPointer			ptr;
	XmScrollBarCallbackStruct	*cbs;
#endif
{
    /* Future:  Need to scroll the correct screen! */
    xvi_screen	*cur_screen = (xvi_screen *) ptr;
    IP_BUF	ipb;

    /* if we are still processing messages from core, skip this event
     * (see comments near __vi_set_scroll_block())
     */
    if ( scroll_block ) {
	return;
    }
    __vi_set_scroll_block();

#ifdef TRACE
    switch ( cbs->reason ) {
	case XmCR_VALUE_CHANGED:
	    vtrace( "scrollbar VALUE_CHANGED %d\n", cbs->value );
	    break;
	case XmCR_DRAG:
	    vtrace( "scrollbar DRAG %d\n", cbs->value );
	    break;
	default:
	    vtrace( "scrollbar <default> %d\n", cbs->value );
	    break;
    }
    vtrace("scrollto {%d}\n", cbs->value );
#endif

    /* Send the new cursor position. */
    ipb.code = VI_C_SETTOP;
    ipb.val1 = cbs->value;
    (void)vi_send(vi_ofd, "1", &ipb);
}


#if defined(__STDC__)
static	xvi_screen	*create_screen( Widget parent, int rows, int cols )
#else
static	xvi_screen	*create_screen( parent, rows, cols )
	Widget		parent;
	int		rows, cols;
#endif
{
    xvi_screen	*new_screen = (xvi_screen *) calloc( 1, sizeof(xvi_screen) );
    Widget	frame;

    /* init... */
    new_screen->color		= COLOR_STANDARD;
    new_screen->parent		= parent;

    /* figure out the sizes */
    new_screen->rows		= rows;
    new_screen->cols		= cols;
    new_screen->ch_width	= font->max_bounds.width;
    new_screen->ch_height	= font->descent + font->ascent;
    new_screen->ch_descent	= font->descent;
    new_screen->clip		= NULL;

    /* allocate and init the backing stores */
    resize_backing_store( new_screen );

    /* set up a translation table for the X toolkit */
    if ( area_trans == NULL ) 
	area_trans = XtParseTranslationTable(areaTrans);

    /* future, new screen gets inserted into the parent sash
     * immediately after the current screen.  Default Pane action is
     * to add it to the end
     */

    /* use a form to hold the drawing area and the scrollbar */
    new_screen->form = XtVaCreateManagedWidget( "form",
	    xmFormWidgetClass,
	    parent,
	    XmNpaneMinimum,		2*new_screen->ch_height,
	    XmNallowResize,		True,
	    NULL
	    );

    /* create a scrollbar. */
    new_screen->scroll = XtVaCreateManagedWidget( "scroll",
	    xmScrollBarWidgetClass,
	    new_screen->form,
	    XmNtopAttachment,		XmATTACH_FORM,
	    XmNbottomAttachment,	XmATTACH_FORM,
	    XmNrightAttachment,		XmATTACH_FORM,
	    XmNminimum,			1,
	    XmNmaximum,			2,
	    XmNsliderSize,		1,
	    NULL
	    );
    XtAddCallback( new_screen->scroll,
		   XmNvalueChangedCallback,
		   scrollbar_moved,
		   new_screen
		   );
    XtAddCallback( new_screen->scroll,
		   XmNdragCallback,
		   scrollbar_moved,
		   new_screen
		   );

    /* create a frame because they look nice */
    frame = XtVaCreateManagedWidget( "frame",
	    xmFrameWidgetClass,
	    new_screen->form,
	    XmNshadowType,		XmSHADOW_ETCHED_IN,
	    XmNtopAttachment,		XmATTACH_FORM,
	    XmNbottomAttachment,	XmATTACH_FORM,
	    XmNleftAttachment,		XmATTACH_FORM,
	    XmNrightAttachment,		XmATTACH_WIDGET,
	    XmNrightWidget,		new_screen->scroll,
	    NULL
	    );

    /* create a drawing area into which we will put text */
    new_screen->area = XtVaCreateManagedWidget( "screen",
	    xmDrawingAreaWidgetClass,
	    frame,
	    XmNheight,		new_screen->ch_height * new_screen->rows,
	    XmNwidth,		new_screen->ch_width * new_screen->cols,
	    XmNtranslations,	area_trans,
	    XmNuserData,	new_screen,
	    XmNnavigationType,	XmNONE,
	    XmNtraversalOn,	False,
	    NULL
	    );

    /* this callback is for when the drawing area is resized */
    XtAddCallback( new_screen->area,
		   XmNresizeCallback,
		   resize_func,
		   new_screen
		   );

    /* this callback is for when the drawing area is exposed */
    XtAddCallback( new_screen->area,
		   XmNexposeCallback,
		   __vi_expose_func,
		   new_screen
		   );

    /* this callback is for when we expose obscured bits 
     * (e.g. there is a window over part of our drawing area
     */
    XtAddEventHandler( new_screen->area,
		       0,	/* no standard events */
		       True,	/* we *WANT* GraphicsExpose */
		       xexpose,	/* what to do */
		       new_screen
		       );

    return new_screen;
}


static	xvi_screen	*split_screen(void)
{
    Cardinal	num;
    WidgetList	c;
    int		rows = __vi_screen->rows / 2;
    xvi_screen	*new_screen;

    /* Note that (global) cur_screen needs to be correctly set so that
     * insert_here knows which screen to put the new one after
     */
    new_screen = create_screen( __vi_screen->parent,
				rows,
				__vi_screen->cols
				);

    /* what are the screens? */
    XtVaGetValues( __vi_screen->parent,
		   XmNnumChildren,	&num,
		   XmNchildren,		&c,
		   NULL
		   );

    /* unmanage all children in preparation for resizing */
    XtUnmanageChildren( c, num );

    /* force resize of the affected screens */
    XtVaSetValues( new_screen->form,
		   XmNheight,	new_screen->ch_height * rows,
		   NULL
		   );
    XtVaSetValues( __vi_screen->form,
		   XmNheight,	__vi_screen->ch_height * rows,
		   NULL
		   );

    /* re-manage */
    XtManageChildren( c, num );

    /* done */
    return new_screen;
}


/* Tell me where to insert the next subpane */
#if defined(__STDC__)
static	Cardinal	insert_here( Widget wid )
#else
static	Cardinal	insert_here( wid )
	Widget		wid;
#endif
{
    Cardinal	i, num;
    WidgetList	c;

    XtVaGetValues( XtParent(wid),
		   XmNnumChildren,	&num,
		   XmNchildren,		&c,
		   NULL
		   );

    /* The  default  XmNinsertPosition  procedure  for  PanedWindow
     * causes sashes to be inserted at the end of the list of children
     * and causes non-sash widgets to be inserted after  other
     * non-sash children but before any sashes.
     */
    if ( ! XmIsForm( wid ) )
	return num;

    /* We will put the widget after the one with the current screen */
    for (i=0; i<num && XmIsForm(c[i]); i++) {
	if ( __vi_screen == NULL || __vi_screen->form == c[i] )
	    return i+1;	/* after the i-th */
    }

    /* could not find it?  this should never happen */
    return num;
}


/*
 * vi_create_editor --
 *	Create the necessary widgetry.
 *
 * PUBLIC: Widget vi_create_editor __P((String, Widget, void (*)(void)));
 */
Widget
vi_create_editor(String name, Widget parent, void (*exitp) (void))
{
    Widget	pane_w;
    Display	*display = XtDisplay( parent );

    __vi_exitp = exitp;

    /* first time through? */
    if ( ctx == NULL ) {

	/* save this for later */
	ctx = XtWidgetToApplicationContext( parent );

	/* add our own special actions */
	XtAppAddActions( ctx, area_actions, XtNumber(area_actions) );

	/* how long is double-click? */
	multi_click_length = XtGetMultiClickTime( display );

	/* check the resource database for interesting resources */
	__XutConvertResources( parent,
			     vi_progname,
			     resource,
			     XtNumber(resource)
			     );

	/* we need a context for moving bits around in the windows */
	__vi_copy_gc = XCreateGC( display,
				 DefaultRootWindow(display),
				 0,
				 0
				 );

	/* routines for inter client communications conventions */
	__vi_InitCopyPaste( f_copy, f_paste, f_clear, fprintf );
    }

    /* create the paned window */
    pane_w = XtVaCreateManagedWidget( "pane",
				      xmPanedWindowWidgetClass,
				      parent,
				      XmNinsertPosition,	insert_here,
				      NULL
				      );

    /* allocate our data structure.  in the future we will have several
     * screens running around at the same time
     */
    __vi_screen = create_screen( pane_w, 24, 80 );

    /* force creation of our color text context */
    set_gc_colors( __vi_screen, COLOR_STANDARD );

    /* done */
    return pane_w;
}


/* These routines deal with the selection buffer */

static	int	selection_start, selection_end, selection_anchor;
static	enum	select_enum {
	    select_char, select_word, select_line
	}	select_type = select_char;
static	int	last_click;

static	char	*clipboard = NULL;
static	int	clipboard_size = 0,
		clipboard_length;


#if defined(__STDC__)
static	void	copy_to_clipboard( xvi_screen *cur_screen )
#else
static	void	copy_to_clipboard( cur_screen )
xvi_screen	*cur_screen;
#endif
{
    /* for now, copy from the backing store.  in the future,
     * vi core will tell us exactly what the selection buffer contains
     */
    clipboard_length = 1 + selection_end - selection_start;

    if ( clipboard == NULL )
	clipboard = (char *) malloc( clipboard_length );
    else if ( clipboard_size < clipboard_length )
	clipboard = (char *) realloc( clipboard, clipboard_length );

    memcpy( clipboard,
	    cur_screen->characters + selection_start,
	    clipboard_length
	    );
}


#if defined(__STDC__)
static	void	mark_selection( xvi_screen *cur_screen, int start, int end )
#else
static	void	mark_selection( cur_screen, start, end )
xvi_screen	*cur_screen;
int		start;
int		end;
#endif
{
    int	row, col, i;

    for ( i=start; i<=end; i++ ) {
	if ( !( cur_screen->flags[i] & COLOR_SELECT ) ) {
	    cur_screen->flags[i] |= COLOR_SELECT;
	    ToRowCol( cur_screen, i, row, col );
	    __vi_draw_text( cur_screen, row, col, 1 );
	}
    }
}


#if defined(__STDC__)
static	void	erase_selection( xvi_screen *cur_screen, int start, int end )
#else
static	void	erase_selection( cur_screen, start, end )
xvi_screen	*cur_screen;
int		start;
int		end;
#endif
{
    int	row, col, i;

    for ( i=start; i<=end; i++ ) {
	if ( cur_screen->flags[i] & COLOR_SELECT ) {
	    cur_screen->flags[i] &= ~COLOR_SELECT;
	    ToRowCol( cur_screen, i, row, col );
	    __vi_draw_text( cur_screen, row, col, 1 );
	}
    }
}


#if defined(__STDC__)
static	void	left_expand_selection( xvi_screen *cur_screen, int *start )
#else
static	void	left_expand_selection( cur_screen, start )
xvi_screen	*cur_screen;
int		*start;
#endif
{
    int row, col;

    switch ( select_type ) {
	case select_word:
	    if ( *start == 0 || isspace( cur_screen->characters[*start] ) )
		return;
	    for (;;) {
		if ( isspace( cur_screen->characters[*start-1] ) )
		    return;
		if ( --(*start) == 0 )
		   return;
	    }
	case select_line:
	    ToRowCol( cur_screen, *start, row, col );
	    col = 0;
	    *start = Linear( cur_screen, row, col );
	    break;
    }
}


#if defined(__STDC__)
static	void	right_expand_selection( xvi_screen *cur_screen, int *end )
#else
static	void	right_expand_selection( cur_screen, end )
xvi_screen	*cur_screen;
int		*end;
#endif
{
    int row, col, last = cur_screen->cols * cur_screen->rows - 1;

    switch ( select_type ) {
	case select_word:
	    if ( *end == last || isspace( cur_screen->characters[*end] ) )
		return;
	    for (;;) {
		if ( isspace( cur_screen->characters[*end+1] ) )
		    return;
		if ( ++(*end) == last )
		   return;
	    }
	case select_line:
	    ToRowCol( cur_screen, *end, row, col );
	    col = cur_screen->cols -1;
	    *end = Linear( cur_screen, row, col );
	    break;
    }
}


#if defined(__STDC__)
static	void	select_start( Widget widget, 
			      XEvent *event,
			      String str, 
			      Cardinal *cardinal
			      )
#else
static	void	select_start( widget, event, str, cardinal )
Widget		widget;   
XEvent		*event;
String		str; 
Cardinal        *cardinal;
#endif
{
    IP_BUF		ipb;
    int			xpos, ypos;
    XPointerMovedEvent	*ev = (XPointerMovedEvent *) event;
    static int		last_click;

    /*
     * NOTE: when multiple panes are implemented, we need to find the correct
     * screen.  For now, there is only one.
     */
    xpos = COLUMN( __vi_screen, ev->x );
    ypos = ROW( __vi_screen, ev->y );

    /* Remove the old one. */
    erase_selection( __vi_screen, selection_start, selection_end );

    /* Send the new cursor position. */
    ipb.code = VI_MOUSE_MOVE;
    ipb.val1 = ypos;
    ipb.val2 = xpos;
    (void)vi_send(vi_ofd, "12", &ipb);

    /* click-click, and we go for words, lines, etc */
    if ( ev->time - last_click < multi_click_length )
	select_type = (enum select_enum) ((((int)select_type)+1)%3);
    else
	select_type = select_char;
    last_click = ev->time;

    /* put the selection here */
    selection_anchor	= Linear( __vi_screen, ypos, xpos );
    selection_start	= selection_anchor;
    selection_end	= selection_anchor;

    /* expand to include words, line, etc */
    left_expand_selection( __vi_screen, &selection_start );
    right_expand_selection( __vi_screen, &selection_end );

    /* draw the new one */
    mark_selection( __vi_screen, selection_start, selection_end );

    /* and tell the window manager we own the selection */
    if ( select_type != select_char ) {
	__vi_AcquirePrimary( widget );
	copy_to_clipboard( __vi_screen );
    }
}


#if defined(__STDC__)
static	void	select_extend( Widget widget, 
			       XEvent *event,
			       String str, 
			       Cardinal *cardinal
			       )
#else
static	void	select_extend( widget, event, str, cardinal )
Widget		widget;   
XEvent		*event;
String		str; 
Cardinal        *cardinal;
#endif
{
    int			xpos, ypos, pos;
    XPointerMovedEvent	*ev = (XPointerMovedEvent *) event;

    /* NOTE:  when multiple panes are implemented, we need to find
     * the correct screen.  For now, there is only one.
     */
    xpos = COLUMN( __vi_screen, ev->x );
    ypos = ROW( __vi_screen, ev->y );

    /* deal with words, lines, etc */
    pos = Linear( __vi_screen, ypos, xpos );
    if ( pos < selection_anchor )
	left_expand_selection( __vi_screen, &pos );
    else
	right_expand_selection( __vi_screen, &pos );

    /* extend from before the start? */
    if ( pos < selection_start ) {
	mark_selection( __vi_screen, pos, selection_start-1 );
	selection_start = pos;
    }

    /* extend past the end? */
    else if ( pos > selection_end ) {
	mark_selection( __vi_screen, selection_end+1, pos );
	selection_end = pos;
    }

    /* between the anchor and the start? */
    else if ( pos < selection_anchor ) {
	erase_selection( __vi_screen, selection_start, pos-1 );
	selection_start = pos;
    }

    /* between the anchor and the end? */
    else {
	erase_selection( __vi_screen, pos+1, selection_end );
	selection_end = pos;
    }

    /* and tell the window manager we own the selection */
    __vi_AcquirePrimary( widget );
    copy_to_clipboard( __vi_screen );
}


#if defined(__STDC__)
static	void	select_paste( Widget widget, 
			      XEvent *event,
			      String str, 
			      Cardinal *cardinal
			      )
#else
static	void	select_paste( widget, event, str, cardinal )
Widget		widget;   
XEvent		*event;
String		str; 
Cardinal        *cardinal;
#endif
{
    __vi_PasteFromClipboard( widget );
}


/* Interface to copy and paste
 * (a) callbacks from the window manager
 *	f_copy	-	it wants our buffer
 *	f_paste	-	it wants us to paste some text
 *	f_clear	-	we've lost the selection, clear it
 */

#if defined(__STDC__)
static	void	f_copy( String *buffer, int *len )
#else
static	void	f_copy( buffer, len )
	String	*buffer;
	int	*len;
#endif
{
#ifdef TRACE
    vtrace("f_copy() called");
#endif
    *buffer	= clipboard;
    *len	= clipboard_length;
}



static	void	f_paste(int widget, int buffer, int length)
{
    /* NOTE:  when multiple panes are implemented, we need to find
     * the correct screen.  For now, there is only one.
     */
#ifdef TRACE
    vtrace("f_paste() called with '%*.*s'\n", length, length, buffer);
#endif
}


#if defined(__STDC__)
static	void	f_clear( Widget widget )
#else
static	void	f_clear( widget )
Widget	widget;
#endif
{
    xvi_screen	*cur_screen;

#ifdef TRACE
    vtrace("f_clear() called");
#endif

    XtVaGetValues( widget, XmNuserData, &cur_screen, 0 );

    erase_selection( cur_screen, selection_start, selection_end );
}


/*
 * These routines deal with the cursor.
 *
 * PUBLIC: void __vi_set_cursor __P((xvi_screen *, int));
 */
void
__vi_set_cursor(xvi_screen *cur_screen, int is_busy)
{
    XDefineCursor( XtDisplay(cur_screen->area),
		   XtWindow(cur_screen->area),
		   (is_busy) ? busy_cursor : std_cursor
		   );
}



/* hooks for the tags widget */

static	String	cur_word = NULL;

/*
 * PUBLIC: void __vi_set_word_at_caret __P((xvi_screen *));
 */
void
__vi_set_word_at_caret(xvi_screen *this_screen)
{
    char	*start, *end, save;
    int		newx, newy;

    newx = this_screen->curx;
    newy = this_screen->cury;

    /* Note that this really ought to be done by core due to wrapping issues */
    for ( end = start = CharAt( this_screen, newy, newx );
	  (isalnum( *end ) || *end == '_') && (newx < this_screen->cols);
	  end++, newx++
	  );
    save = *end;
    *end = '\0';
    if ( cur_word != NULL ) free( cur_word );
    cur_word = strdup( start );
    *end = save;

    /* if the tag stack widget is active, set the text field there
     * to agree with the current caret position.
     */
    __vi_set_tag_text( cur_word );
}


String	__vi_get_word_at_caret(xvi_screen *this_screen)
{
    return (cur_word) ? cur_word : "";
}


/*
 * These routines deal with the caret.
 *
 * PUBLIC: void draw_caret __P((xvi_screen *));
 */
static void
draw_caret(xvi_screen *this_screen)
{
    /* draw the caret by drawing the text in highlight color */
    *FlagAt( this_screen, this_screen->cury, this_screen->curx ) |= COLOR_CARET;
    __vi_draw_text( this_screen, this_screen->cury, this_screen->curx, 1 );
}

/*
 * PUBLIC: void __vi_erase_caret __P((xvi_screen *));
 */
void
__vi_erase_caret(xvi_screen *this_screen)
{
    /* erase the caret by drawing the text in normal video */
    *FlagAt( this_screen, this_screen->cury, this_screen->curx ) &= ~COLOR_CARET;
    __vi_draw_text( this_screen, this_screen->cury, this_screen->curx, 1 );
}

/*
 * PUBLIC: void	__vi_move_caret __P((xvi_screen *, int, int));
 */
void
__vi_move_caret(xvi_screen *this_screen, int newy, int newx)
{
    /* remove the old caret */
    __vi_erase_caret( this_screen );

    /* caret is now here */
    this_screen->curx = newx;
    this_screen->cury = newy;
    draw_caret( this_screen );
}
