
/***********************************************************
Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts,
and the Massachusetts Institute of Technology, Cambridge, Massachusetts.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Digital or MIT not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

******************************************************************/

#include <stdio.h>
#include <ctype.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/XawInit.h>
#include "KeyWSinkP.h"
#include <X11/Xaw/AsciiSinkP.h>
#include <X11/Xaw/AsciiSrcP.h>	/* For source function defs. */
#include <X11/Xaw/TextP.h>	/* I also reach into the text widget. */

#ifdef GETLASTPOS
#undef GETLASTPOS		/* We will use our own GETLASTPOS. */
#endif

#define GETLASTPOS XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, TRUE)

static void Initialize(), Destroy();
static Boolean SetValues();

static void DisplayText();


#define offset(field) XtOffsetOf(KeyWSinkRec, keyw_sink.field)

static XtResource resources[] = {
    {XtNwordList, XtCWordList, XtRPointer, sizeof(char **),
	offset(wordList), XtRImmediate, (XtPointer) NULL}
};
#undef offset

#define SuperClass		(&asciiSinkClassRec)
KeyWSinkClassRec keyWSinkClassRec = {
  {
/* core_class fields */	
    /* superclass	  	*/	(WidgetClass) SuperClass,
    /* class_name	  	*/	"KeyWSink",
    /* widget_size	  	*/	sizeof(KeyWSinkRec),
    /* class_initialize   	*/	XawInitializeWidgetSet,
    /* class_part_initialize	*/	NULL,
    /* class_inited       	*/	FALSE,
    /* initialize	  	*/	Initialize,
    /* initialize_hook		*/	NULL,
    /* obj1		  	*/	NULL,
    /* obj2		  	*/	NULL,
    /* obj3		  	*/	0,
    /* resources	  	*/	resources,
    /* num_resources	  	*/	XtNumber(resources),
    /* xrm_class	  	*/	NULLQUARK,
    /* obj4		  	*/	FALSE,
    /* obj5		  	*/	FALSE,
    /* obj6			*/	FALSE,
    /* obj7		  	*/	FALSE,
    /* destroy		  	*/	Destroy,
    /* obj8		  	*/	NULL,
    /* obj9		  	*/	NULL,
    /* set_values	  	*/	SetValues,
    /* set_values_hook		*/	NULL,
    /* obj10			*/	NULL,
    /* get_values_hook		*/	NULL,
    /* obj11		 	*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private   	*/	NULL,
    /* obj12		   	*/	NULL,
    /* obj13			*/	NULL,
    /* obj14			*/	NULL,
    /* extension		*/	NULL
  },
/* text_sink_class fields */
  {
    /* DisplayText              */      DisplayText,
    /* InsertCursor             */      XtInheritInsertCursor,
    /* ClearToBackground        */      XtInheritClearToBackground,
    /* FindPosition             */      XtInheritFindPosition,
    /* FindDistance             */      XtInheritFindDistance,
    /* Resolve                  */      XtInheritResolve,
    /* MaxLines                 */      XtInheritMaxLines,
    /* MaxHeight                */      XtInheritMaxHeight,
    /* SetTabs                  */      XtInheritSetTabs,
    /* GetCursorBounds          */      XtInheritGetCursorBounds
  },
/* ascii_sink_class fields */
  {
    /* unused			*/	0
  },
/* keyw_sink_class fields */
  {
    /* unused			*/	0
  }
};

WidgetClass keyWSinkObjectClass = (WidgetClass)&keyWSinkClassRec;

/* Utilities */


/* matchw
   given a string buffer and a null-terminated list of words, find
   the leftmost position in the buffer that matches one of the words
   in the list, if any.  Returns the offset in the buffer of first match,
   -1 if no match.  Also, the int* parameter matchLen is set to the
   length of the match. */

static int
matchw(buf, len, words, matchLen)
char	*buf;
int	len;
char	*words[];
int	*matchLen;
{
	int	i, j, k;
	int	left = len;
	int	wLen, last;
	char	*start=buf, *bp, *wp;
	int	match, tested;

	/* loop through the characters in the buffer */

	*matchLen = 0;
	if (words == NULL) return -1;

	while (left > 0) {

		/* loop through each word in the word list */

		tested = False;
		for (k=0; words[k]!=NULL; k++) {

			/* if too few characters left in buffer, next word */

			wLen = strlen(words[k]);
			if ((last = left - wLen + 1) <= 0) continue;
			tested = True;

			/* loop through each character in the word */

			match = True;
			bp=buf; wp=words[k]; 
			for (i=0; i < wLen; i++) {
			    if ((wp[i] != bp[i]) && (toupper(wp[i]) != bp[i])) {
					match = False;
					break;
				}
			}

			if (match) {
				*matchLen = wLen;
				return (buf - start);
			}
		}

		if (! tested) return -1;

		left--;
		buf++;
	}

	return -1;
}


static int 
CharWidth (w, x, c)
Widget w;
int x;
unsigned char c;
{
    register int    i, width, nonPrinting;
    KeyWSinkObject sink = (KeyWSinkObject) w;
    XFontStruct *font = sink->text_sink.font;
    Position *tab;

    if ( c == XawLF ) return(0);

    if (c == XawTAB) {
	/* Adjust for Left Margin. */
	x -= ((TextWidget) XtParent(w))->text.margin.left;

	if (x >= (int)XtParent(w)->core.width) return 0;
	for (i = 0, tab = sink->text_sink.tabs ; 
	     i < sink->text_sink.tab_count ; i++, tab++) {
	    if (x < *tab) {
		if (*tab < (int)XtParent(w)->core.width)
		    return *tab - x;
		else
		    return 0;
	    }
	}
	return 0;
    }

    if ( (nonPrinting = (c < (unsigned char) XawSP)) )
	if (sink->ascii_sink.display_nonprinting)
	    c += '@';
	else {
	    c = XawSP;
	    nonPrinting = False;
	}

    if (font->per_char &&
	    (c >= font->min_char_or_byte2 && c <= font->max_char_or_byte2))
	width = font->per_char[c - font->min_char_or_byte2].width;
    else
	width = font->min_bounds.width;

    if (nonPrinting)
	width += CharWidth(w, x, (unsigned char) '^');

    return width;
}

/*	Function Name: PaintText
 *	Description: Actually paints the text into the windoe.
 *	Arguments: w - the text widget.
 *                 gc - gc to paint text with.
 *                 x, y - location to paint the text.
 *                 buf, len - buffer and length of text to paint.
 *	Returns: the width of the text painted, or 0.
 *
 * NOTE:  If this string attempts to paint past the end of the window
 *        then this function will return zero.
 */

static Dimension
PaintText(w, gc, x, y, buf, len)
Widget w;
GC gc;
Position x, y;
unsigned char * buf;
int len;
{
    KeyWSinkObject sink = (KeyWSinkObject) w;
    TextWidget ctx = (TextWidget) XtParent(w);

    Position max_x;
    Dimension width = XTextWidth(sink->text_sink.font, (char *) buf, len); 
    max_x = (Position) ctx->core.width;

    if ( ((int) width) <= -x)	           /* Don't draw if we can't see it. */
      return(width);

    XDrawImageString(XtDisplay(ctx), XtWindow(ctx), gc, 
		     (int) x, (int) y, (char *) buf, len);
    if ( (((Position) width + x) > max_x) && (ctx->text.margin.right != 0) ) {
	x = ctx->core.width - ctx->text.margin.right;
	width = ctx->text.margin.right;
	XFillRectangle(XtDisplay((Widget) ctx), XtWindow( (Widget) ctx),
		       sink->ascii_sink.normgc, (int) x,
		       (int) y - sink->text_sink.font->ascent, 
		       (unsigned int) width,
		       (unsigned int) (sink->text_sink.font->ascent +
				       sink->text_sink.font->descent));
	return(0);
    }
    return(width);
}

/*	Function Name: PaintKeyW
 *	Description: highlight selected list of keywords, then calls function
 *                   to actually paint the text into the window.
 *	Arguments: w - the text widget.
 *                 gc - gc to paint text with.
 *                 invgc - gc to highlight text with.
 *                 x, y - location to paint the text.
 *                 buf, len - buffer and length of text to paint.
 *	Returns: the width of the text painted, or 0.
 *
 * NOTE:  If this string attempts to paint past the end of the window
 *        then this function will return zero.
 */

static Dimension
PaintKeyW(w, gc, invgc, x, y, buf, len)
Widget w;
GC gc, invgc;
Position x, y;
unsigned char * buf;
int len;
{
    KeyWSinkObject sink = (KeyWSinkObject) w;
    TextWidget ctx = (TextWidget) XtParent(w);
    int	first, nch;
    Dimension px, xlen=0;

    first = matchw(buf, len, sink->keyw_sink.wordList, &nch);
    while (first != -1) {
	if (first != 0) {
		if ((px = PaintText(w, gc, x+xlen, y, buf, first)) == 0)
								return 0;
		    xlen += px;
		    len -= first;
		    buf += first;
	}
        if ((px = PaintText(w, invgc, x+xlen, y, buf, nch)) == 0) return 0;
	    xlen += px;
	    len -= nch;
	    buf += nch;

        first = matchw(buf, len, sink->keyw_sink.wordList, &nch);
    }

    if (len > 0) {
	if ((px = PaintText(w, gc, x+xlen, y, buf, len)) == 0) return 0;
	    xlen += px;
    }

    return xlen;
}

/* Sink Object Functions */

/*
 * This function does not know about drawing more than one line of text.
 */
 
static void 
DisplayText(w, x, y, pos1, pos2, highlight)
Widget w;
Position x, y;
Boolean highlight;
XawTextPosition pos1, pos2;
{
    KeyWSinkObject sink = (KeyWSinkObject) w;
    Widget source = XawTextGetSource(XtParent(w));
    unsigned char buf[BUFSIZ];

    int j, k;
    XawTextBlock blk;
    GC gc = highlight ? sink->ascii_sink.invgc : sink->ascii_sink.normgc;
    GC invgc = highlight ? sink->ascii_sink.normgc : sink->ascii_sink.invgc;

    if (!sink->ascii_sink.echo) return;

    y += sink->text_sink.font->ascent;
    for ( j = 0 ; pos1 < pos2 ; ) {
	pos1 = XawTextSourceRead(source, pos1, &blk, pos2 - pos1);
	for (k = 0; k < blk.length; k++) {
	    if (j >= BUFSIZ) {	/* buffer full, dump the text. */
		if (sink->keyw_sink.wordList == NULL)
	            x += PaintText(w, gc, x, y, buf, j);
		else
	            x += PaintKeyW(w, gc, invgc, x, y, buf, j);
		j = 0;
	    }
	    buf[j] = blk.ptr[k];
	    if (buf[j] == XawLF)	/* line feeds ('\n') are not printed. */
	        continue;

	    else if (buf[j] == '\t') {
	        Position temp = 0;
		Dimension width;

		/*
	        if ((j != 0) && ((temp = PaintText(w, gc, x, y, buf, j)) == 0))
		  return;
		*/
	        if (j != 0) {
		    if (sink->keyw_sink.wordList == NULL)
			temp = PaintText(w, gc, x, y, buf, j);
		    else
			temp = PaintKeyW(w, gc, invgc, x, y, buf, j);
		    if (temp == 0) return;
		}

	        x += temp;
		width = CharWidth(w, x, (unsigned char) '\t');
		XFillRectangle(XtDisplayOfObject(w), XtWindowOfObject(w),
			       invgc, (int) x,
			       (int) y - sink->text_sink.font->ascent,
			       (unsigned int) width,
			       (unsigned int) (sink->text_sink.font->ascent +
					       sink->text_sink.font->descent));
		x += width;
		j = -1;
	    }
	    else if ( buf[j] < (unsigned char) ' ' ) {
	        if (sink->ascii_sink.display_nonprinting) {
		    buf[j + 1] = buf[j] + '@';
		    buf[j] = '^';
		    j++;
		}
		else
		    buf[j] = ' ';
	    }
	    j++;
	}
    }
    if (j > 0)
	if (sink->keyw_sink.wordList == NULL)
            (void) PaintText(w, gc, x, y, buf, j);
	else
            (void) PaintKeyW(w, gc, invgc, x, y, buf, j);
}

#define insertCursor_width 6
#define insertCursor_height 3
static char insertCursor_bits[] = {0x0c, 0x1e, 0x33};

static Pixmap
CreateInsertCursor(s)
Screen *s;
{
    return (XCreateBitmapFromData (DisplayOfScreen(s), RootWindowOfScreen(s),
		  insertCursor_bits, insertCursor_width, insertCursor_height));
}


static void
GetGC(sink)
KeyWSinkObject sink;
{
    XtGCMask valuemask = (GCFont | 
			  GCGraphicsExposures | GCForeground | GCBackground );
    XGCValues values;

    values.font = sink->text_sink.font->fid;
    values.graphics_exposures = (Bool) FALSE;
    
    values.foreground = sink->text_sink.foreground;
    values.background = sink->text_sink.background;
    sink->ascii_sink.normgc = XtGetGC((Widget)sink, valuemask, &values);
    
    values.foreground = sink->text_sink.background;
    values.background = sink->text_sink.foreground;
    sink->ascii_sink.invgc = XtGetGC((Widget)sink, valuemask, &values);
    
    values.function = GXxor;
    values.background = (unsigned long) 0L;	/* (pix ^ 0) = pix */
    values.foreground = (sink->text_sink.background ^ 
			 sink->text_sink.foreground);
    valuemask = GCGraphicsExposures | GCFunction | GCForeground | GCBackground;
    
    sink->ascii_sink.xorgc = XtGetGC((Widget)sink, valuemask, &values);
}


/***** Public routines *****/

/*	Function Name: Initialize
 *	Description: Initializes the TextSink Object.
 *	Arguments: request, new - the requested and new values for the object
 *                                instance.
 *	Returns: none.
 *
 */

/* ARGSUSED */
static void
Initialize(request, new)
Widget request, new;
{
    KeyWSinkObject sink = (KeyWSinkObject) new;

    GetGC(sink);
    
    sink->ascii_sink.insertCursorOn= CreateInsertCursor(XtScreenOfObject(new));
    sink->ascii_sink.laststate = XawisOff;
    sink->ascii_sink.cursor_x = sink->ascii_sink.cursor_y = 0;
}

/*	Function Name: Destroy
 *	Description: This function cleans up when the object is 
 *                   destroyed.
 *	Arguments: w - the KeyWSink Object.
 *	Returns: none.
 */

static void
Destroy(w)
Widget w;
{
   KeyWSinkObject sink = (KeyWSinkObject) w;

   XtReleaseGC(w, sink->ascii_sink.normgc);
   XtReleaseGC(w, sink->ascii_sink.invgc);
   XtReleaseGC(w, sink->ascii_sink.xorgc);
   XFreePixmap(XtDisplayOfObject(w), sink->ascii_sink.insertCursorOn);
}

/*	Function Name: SetValues
 *	Description: Sets the values for the KeyWSink
 *	Arguments: current - current state of the object.
 *                 request - what was requested.
 *                 new - what the object will become.
 *	Returns: True if redisplay is needed.
 */

/* ARGSUSED */
static Boolean
SetValues(current, request, new)
Widget current, request, new;
{
    KeyWSinkObject w = (KeyWSinkObject) new;
    KeyWSinkObject old_w = (KeyWSinkObject) current;

    if (w->text_sink.font != old_w->text_sink.font
	|| w->text_sink.background != old_w->text_sink.background
	|| w->text_sink.foreground != old_w->text_sink.foreground) {
	XtReleaseGC((Widget)w, w->ascii_sink.normgc);
	XtReleaseGC((Widget)w, w->ascii_sink.invgc);
	XtReleaseGC((Widget)w, w->ascii_sink.xorgc);
	GetGC(w);
	((TextWidget)XtParent(new))->text.redisplay_needed = True;
    } else {
	if ( (w->ascii_sink.echo != old_w->ascii_sink.echo) ||
	     (w->ascii_sink.display_nonprinting != 
                                     old_w->ascii_sink.display_nonprinting) )
	    ((TextWidget)XtParent(new))->text.redisplay_needed = True;
    }
    
    return False;
}
