/* 
 * sxEntry.c --
 *
 *	This file contains the code that implements text entry
 *	subwindows for the Sx package.  Text entries are windows
 *	that display a label and allow the user to type in (and
 *	edit) a textual value to associate with the label.
 *
 * Copyright (C) 1986 Regents of the University of California.
 * 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.  The University of California makes
 * no representations about the suitability of this software for
 * any purpose.  It is provided "as is" without express or
 * implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/sx/RCS/sxEntry.c,v 1.11 90/04/19 11:23:35 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <ctype.h>
#include <stdlib.h>
#include <strings.h>
#include "sx.h"
#include "sxInt.h"

/*
 * Library imports:
 */

extern char *malloc();

/*
 * A record of the following type is used to store information about
 * each entry.
 */

typedef struct {
    Display *display;		/* Connection to X server. */
    Window w;			/* Window used by entry. */
    int width, height;		/* Dimensions of w, in pixels. */
    char *label;		/* Fixed label for entry (storage is
				 * dynamically allocated).  If NULL, then
				 * no label is displayed. */
    char *text;			/* Pointer to client-owned space in which
				 * to store user type-in. */
    int textSize;		/* Number of bytes available for non-null
				 * characters at text (actual space is this
				 * + 1, to account for null terminator). */
    XFontStruct *fontPtr;	/* Font to use for displaying entry. */
    int x;			/* X-location at which to display left edge
				 * of first character in text. */
    int y;			/* Y-location at which to display text.
				 * Recomputed each time window changes size. */
    unsigned long foreground;	/* Foreground color for display. */
    unsigned long background;	/* Background color for display. */
    GC gc;			/* X graphics context for redisplay. */
    int caret;			/* Index of character whose left edge is
				 * the caret position.  Always >= 0. */
    int caretX, caretY;		/* Location (in pixels, in w) of upper-left
				 * corner of caret pixmap. */
    int selectFirst;		/* First character that's selected.  -1
				 * means nothing's selected. */
    int selectLast;		/* Last character that's selected.  -1
				 * means nothing's selected. */
    int selectAnchor;		/* The right mouse button adjusts one end
				 * of the selection;  this gives the position
				 * of the end that's fixed. */
    int flags;			/* Miscellaneous flag values:  see below. */
} Entry;

/*
 * Flags for entries:
 *
 * FOCUS_WINDOW:		1 means that this window has the focus.
 * CARET_OFF:			1 means that the caret has been turned off,
 *				pending other changes to the window.
 */

#define FOCUS_WINDOW	1
#define CARET_OFF	2

/*
 * How much space to leave between label and left edge of window:
 */

#define LEFT_MARGIN 2

/*
 * The cursor used when the pointer is in entry windows:
 */

#include "bitmaps/ptr"
#include "bitmaps/ptrMask"
static Cursor cursor = 0;

/*
 * Bitmaps used to draw the caret:
 */

#include "bitmaps/caret"
#include "bitmaps/caretMask"
static Pixmap caret;
static Pixmap caretMask;

/*
 * The context below is used to map from X window ids to Entry structures.
 */

static XContext entryContext;

/*
 * The strings below indicate which keystrokes are used for editing
 * operations like copying the selection or deletion, and the .Xdefault
 * names associated with them.
 */

static char *defaultNames[] = {
    "backspace",
    "backspace2",
    "killWord",
    "killLine",
    "copySelection",
    "deleteSelection"
};

static char defBackspace[] =		"\b";
static char defBackspace2[] =		"\\177";
static char defKillWord[] =		"\\Cw";
static char defKillLine[] =		"\\Cu";
static char defCopySelection[] =	"\\Cv";
static char defDeleteSelection[] =	"\\Cd";

static char *bindings[] = {defBackspace, defBackspace2, defKillWord,
	defKillLine, defCopySelection, defDeleteSelection};

#define numKeystrokes (sizeof(defaultNames)/sizeof(char *))

/*
 * Forward references to things defined in this file:
 */

static void		EntryCvtBinding();
static void		EntryDelete();
static void		EntryDrawCaret();
static void		EntryEraseCaret();
static void		EntryEventProc();
static int		EntryFindChar();
static void		EntryFocusProc();
static void		EntryInit();
static void		EntryInsert();
static void		EntryKeyProc();
static void		EntryMouseProc();
static void		EntryRedisplay();
static void		EntrySelChanged();
static int		EntrySelGet();

/*
 *----------------------------------------------------------------------
 *
 * Sx_EntryMake --
 *
 *	Make a window into an entry subwindow.  An entry is a label
 *	with space after it for typing in text.  The text appears in
 *	a string area provided by the caller.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	From this moment on, the entire area of window will be
 *	used to display the entry.  The contents of text will
 *	change spontaneously as the user invokes operations in
 *	the entry window.
 *
 *----------------------------------------------------------------------
 */

void
Sx_EntryMake(display, window, focusWindow, label, fontPtr, foreground,
	background, text, size)
    Display *display;		/* Connection to X server. */
    Window window;		/* Window to use for entry.  If the window
				 * is already in use for an entry, this
				 * call can be used to change the text and
				 * other parameters (the window will be
				 * redisplayed). */
    Window focusWindow;		/* Gives id of window to use as source for
				 * focussing keyboard events. */
    char *label;		/* Text to appear at left edge of window,
				 * labelling the entry.  May be NULL. */
    XFontStruct *fontPtr;	/* Font to use for displaying info in window,
				 * or NULL to use default font. */
    unsigned long foreground;	/* Color to use for displaying text. */
    unsigned long background;	/* Background color for window. */
    char *text;			/* Stuff that user types will be stored here,
				 * null-terminated.  Caller must initialize. */
    int size;			/* Number of bytes of storage at text:
				 * determines longest string that will be
				 * accepted. */
{
    register Entry *entryPtr;
    caddr_t data;
    XGCValues gcValues;

    EntryInit(display);

    if (fontPtr == NULL) {
	fontPtr = Sx_GetDefaultFont(display);
    }

    /*
     * See if this window is already an entry window.  If so, release
     * the fields that will be reallocated.  If not, allocate a new
     * structure and do once-only initialization.
     */

    if (XFindContext(display, window, entryContext, &data) == 0) {
	entryPtr = (Entry *) data;
	if (entryPtr->label != NULL) {
	    free(entryPtr->label);
	}
	XFreeGC(display, entryPtr->gc);
	entryPtr->flags |= CARET_OFF;
    } else {
	XSetWindowAttributes atts;
	Drawable dum1;
	int dum2;

	entryPtr = (Entry *) malloc(sizeof(Entry));
	entryPtr->display = display;
	entryPtr->w = window;
	entryPtr->flags = CARET_OFF;
	Sx_EnableFocus(display, focusWindow, window, EntryFocusProc,
		(ClientData) entryPtr);
	(void) Sx_HandlerCreate(display, window, KeyPressMask,
		EntryKeyProc, (ClientData) entryPtr);
	Sx_HandlerCreate(display, window, StructureNotifyMask|ExposureMask,
		EntryEventProc, (ClientData) entryPtr);
	(void) Sx_HandlerCreate(display, window, ButtonPressMask
		|ButtonReleaseMask|Button1MotionMask|Button3MotionMask,
		EntryMouseProc, (ClientData) entryPtr);
	XGetGeometry(display, window, &dum1, &dum2, &dum2, &entryPtr->width,
		&entryPtr->height, &dum2, &dum2);
	entryPtr->y = (entryPtr->height + fontPtr->ascent
		- fontPtr->descent)/2;
	atts.background_pixel = background;
	atts.cursor = cursor;
	XChangeWindowAttributes(display, window,
	    CWBackPixel|CWCursor, &atts);
	XSaveContext(display, window, entryContext, (caddr_t) entryPtr);
    }

    /*
     * Reset the fields of the entry for the new parameters.
     */

    if (label == NULL) {
	entryPtr->label = NULL;
	entryPtr->x = LEFT_MARGIN;
    } else {
	entryPtr->label = (char *)
		malloc((unsigned) (strlen(label) + 1));
	(void) strcpy(entryPtr->label, label);
	entryPtr->x =  LEFT_MARGIN + XTextWidth(fontPtr, label,
	    strlen(label));
    }
    entryPtr->text = text;
    entryPtr->textSize = size - 1;
    text[entryPtr->textSize] = 0;	/* In case caller didn't terminate. */
    entryPtr->fontPtr = fontPtr;
    entryPtr->foreground = foreground;
    entryPtr->background = background;
    gcValues.foreground = foreground;
    gcValues.background = background;
    gcValues.font = fontPtr->fid;
    entryPtr->gc = XCreateGC(display, window,
	GCForeground|GCBackground|GCFont, &gcValues);
    entryPtr->caret = strlen(entryPtr->text);
    entryPtr->selectFirst = -1;
    entryPtr->selectLast = -1;
    entryPtr->selectAnchor = -1;

    XClearArea(display, window, 0, 0, entryPtr->width, entryPtr->height,
	    False);
    EntryRedisplay(entryPtr, 0, entryPtr->caret - 1, 1);
    EntryDrawCaret(entryPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_EntryCreate --
 *
 *	Like Sx_EntryMake, except also create the window that will hold
 *	the entry.
 *
 * Results:
 *	The return value is the X id for a new window that behaves
 *	as an entry.  It will have the given location and size in
 *	parent.
 *
 * Side effects:
 *	The area of the new window will be used to display the entry.
 *	The contents of text will change spontaneously as the user
 *	invokes operations in the entry window.
 *
 *----------------------------------------------------------------------
 */

Window
Sx_EntryCreate(display, parent, focusWindow, x, y, width, height, borderSize,
	label, fontPtr, foreground, background, text, size)
    Display *display;		/* Connection to X server. */
    Window parent;		/* Parent in which to create entry window. */
    Window focusWindow;		/* Top-level application window to use for
				 * focussing. */
    int x, y;			/* Location of upper left corner of new
				 * window, in coords. of parent. */
    int width, height;		/* Dimensions of new window, in pixels. */
    int borderSize;		/* Width of border for new window. */
    char *label;		/* Text to appear at left edge of window,
				 * labelling the entry. */
    XFontStruct *fontPtr;	/* Font to use for displaying info in window,
				 * or NULL to use default font. */
    unsigned long foreground;	/* Color to use for displaying text. */
    unsigned long background;	/* Background color for window. */
    char *text;			/* Stuff that user types will be stored here,
				 * null-terminated.  Caller must initialize. */
    int size;			/* Number of bytes of storage at text:
				 * determines longest string that will be
				 * accepted. */
{
    Window w;

    w = XCreateSimpleWindow(display, parent, x, y, width, height,
	    borderSize, foreground, background);
    if (w == NULL) {
	Sx_Panic(display, "Sx_EntryCreate:  couldn't create new window.");
    }
    Sx_EntryMake(display, w, focusWindow, label, fontPtr, foreground,
	    background, text, size);
    return w;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryFocusProc --
 *
 *	This procedure is invoked by the Sx focus code when an
 *	entry gets or loses the focus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Turns the caret on or off.
 *
 *----------------------------------------------------------------------
 */

static void
EntryFocusProc(entryPtr, gotFocus)
    register Entry *entryPtr;		/* Entry that just got the focus. */
    int gotFocus;			/* 1 means entry got focus, 0 means
					 * it lost it. */
{
    if (gotFocus) {
	entryPtr->flags |= FOCUS_WINDOW;
	EntryDrawCaret(entryPtr);
    } else {
	entryPtr->flags &= ~FOCUS_WINDOW;
	EntryEraseCaret(entryPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryEventProc --
 *
 *	This procedure is called by the Sx dispatcher to handle
 *	most of the events that occur in entry windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the event.  The entry may get redisplayed, or it
 *	may gets its data structures recycled, or any of several other
 *	things.
 *
 *----------------------------------------------------------------------
 */

static void
EntryEventProc(entryPtr, eventPtr)
    register Entry *entryPtr;		/* Entry for which event occurred. */
    register XEvent *eventPtr;		/* Event that occurred. */
{
    switch (eventPtr->type) {

	case ConfigureNotify:
	    entryPtr->width = eventPtr->xconfigure.width;
	    entryPtr->height = eventPtr->xconfigure.height;
	    entryPtr->y =
		    (eventPtr->xconfigure.height + entryPtr->fontPtr->ascent
		    - entryPtr->fontPtr->descent)/2;
	    break;

	case Expose:
	    XClearArea(entryPtr->display, entryPtr->w, 0, 0, 0, 0, False);
	    EntryRedisplay(entryPtr, 0, strlen(entryPtr->text) - 1, 1);
	    EntryDrawCaret(entryPtr);
	    break;

	case DestroyNotify:
	    if (entryPtr->selectFirst >= 0) {
		entryPtr->selectFirst = entryPtr->selectLast = -1;
		Sx_SelectionClear(entryPtr->display);
	    }
	    XDeleteContext(entryPtr->display, entryPtr->w, entryContext);
	    if (entryPtr->label != NULL) {
		free((char *) entryPtr->label);
	    }
	    XFreeGC(entryPtr->display, entryPtr->gc);
	    free((char *) entryPtr);
	    break;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryMouseProc --
 *
 *	This procedure is invoked by the Sx dispatcher whenever a mouse
 *	button goes down or up and whenever the mouse moves with a button
 *	down.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection and caret get adjusted in response to button
 *	actions.
 *
 *----------------------------------------------------------------------
 */

static void
EntryMouseProc(entryPtr, eventPtr)
    register Entry *entryPtr;		/* Entry for which event occurred. */
    register XButtonEvent *eventPtr;	/* Event that occurred. */
{
    int oldFirst, oldLast, tmp, index;
    static int button = -1;
    static int lastX, lastY;		/* Last place where left button
					 * was clicked.  Used to detect
					 * multiple clicks. */
    static Entry *lastEntryPtr;		/* Last entry clicked in.  Also used
					 * to detect multiple clicks. */
    static int repeatCount = 0;		/* Count of multiple clicks. */
    static unsigned long lastTime;	/* Time of last click.  Mutliple clicks
					 * must be close together in time. */
    static int dragCaret;		/* Should caret drag with pointer?
					 * Set when buttons go down. */
    static int dragSelection;		/* Should selection drag with pointer?
					 * Set when buttons go down. */


    if ((eventPtr->subwindow != NULL)
	    && (eventPtr->subwindow != entryPtr->w)) {
	return;
    }

    if (eventPtr->type == ButtonRelease) {
	if ((eventPtr->button) == button) {
	    button = -1;
	}
	return;
    }

    /*
     * Ignore mouse events if we didn't see the down event.  Count
     * successive clicks in the same place.  Decide whether we're
     * moving the caret or the selection or both.
     */
    
    if (eventPtr->type == ButtonPress) {
	button = eventPtr->button;
	if (button == Button1) {
	    if ((lastEntryPtr == entryPtr)
		    && ((lastX + 1) >= eventPtr->x)
		    && ((lastX - 1) <= eventPtr->x)
		    && ((lastY + 1) >= eventPtr->y)
		    && ((lastY - 1) <= eventPtr->y)
		    && ((eventPtr->time - lastTime) < 500)) {
		repeatCount += 1;
		if (repeatCount > 2) {
		    repeatCount = 0;
		}
	    } else {
		repeatCount = 0;
	    }
	    if (eventPtr->state & ControlMask) {
		dragCaret = 0;
		dragSelection = 1;
	    } else {
		dragCaret = 1;
		dragSelection = 0;
	    }
	    if (repeatCount != 0) {
		dragSelection = 1;
	    }
	} else if (button == Button3) {
	    dragSelection = 1;
	    if (eventPtr->state & Button1Mask) {
		dragCaret = 1;
	    } else {
		dragCaret = 0;
	    }
	} else {
	    button = -1;
	}
	lastX = eventPtr->x;
	lastY = eventPtr->y;
	lastEntryPtr = entryPtr;
	lastTime = eventPtr->time;
    }

    if (button == -1) {
	return;			/* We didn't see the down event. */
    }

    /*
     * Find out what's being pointed at.  It's a little easier to see
     * what you're pointing to if the mouse doesn't have to be right
     * on top of it to select it. Offset the hot spot to accomplish this.
     */

    index = EntryFindChar(entryPtr, eventPtr->x - 2);
    oldFirst = entryPtr->selectFirst;
    oldLast = entryPtr->selectLast;

    if (dragSelection) {
	/*
	 * Must grab selection before figuring out where to put it:
	 * as part of grabbing it, the selection info in entryPtr may
	 * get cleared (if we already had it).
	 */

	if (oldFirst < 0) {
	    Sx_SelectionSet(entryPtr->display, EntrySelGet, EntrySelChanged,
		    (ClientData) entryPtr);
	}

	/*
	 * There are three possibilities when setting the selection:
	 * drag a new selection; drag one edge of the selection with
	 * the caret as the other edge, and drag one edge with the
	 * original selection point as the other edge.
	 */

	if ((button == Button1) || dragCaret) {
	    entryPtr->selectFirst = entryPtr->selectLast = index;
	    entryPtr->selectAnchor = index;
	} else {
	    if ((oldFirst < 0) || !(eventPtr->state & ControlMask)) {
		entryPtr->selectAnchor = entryPtr->caret;
		if (index < entryPtr->selectAnchor) {
		    entryPtr->selectAnchor -= 1;
		}
	    }
	    if (index < entryPtr->selectAnchor) {
		entryPtr->selectFirst = index;
		entryPtr->selectLast = entryPtr->selectAnchor;
	    } else {
		entryPtr->selectFirst = entryPtr->selectAnchor;
		entryPtr->selectLast = index;
	    }
	}

	/*
	 * Depending on how many clicks there have been, round the selection up:
	 * 1st click:		no rounding
	 * 2nd click:		round to word boundary
	 * 3rd click:		round to line boundary
	 */
    
	if ((eventPtr->state & ShiftMask) == 0) {
	    switch(repeatCount) {
		
		case 1: {
		    static char wordMask[16] = {0, 0, 0, 0, 0, 0, 0xff, 0x3,
			    0xfe, 0xff, 0xff, 0x87, 0xfe, 0xff, 0xff, 0x3};
		    static char bit[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20,
			    0x40, 0x80};
		    register char c;
	
		    c = entryPtr->text[entryPtr->selectFirst];
		    if (!(c & 0200) && (wordMask[c>>3] & bit[c&07])) {
			while (entryPtr->selectFirst > 0) {
			    entryPtr->selectFirst--;
			    c = entryPtr->text[entryPtr->selectFirst];
			    if ((c & 0200) || !(wordMask[c>>3] & bit[c&07])) {
				entryPtr->selectFirst++;
				break;
			    }
			}
		    }
		    c = entryPtr->text[entryPtr->selectLast];
		    if ((c != 0) &&
			    !(c & 0200) && (wordMask[c>>3] & bit[c&07])) {
			while (1) {
			    entryPtr->selectLast++;
			    c = entryPtr->text[entryPtr->selectLast];
			    if ((c == 0) || (c & 0200) ||
				    !(wordMask[c>>3] & bit[c&07])) {
				entryPtr->selectLast--;
				break;
			    }
			}
		    }
		    break;
		}
	
		case 2: {
		    entryPtr->selectFirst = 0;
		    entryPtr->selectLast = strlen(entryPtr->text) - 1;
		    break;
		}
	    }
	}

	/*
	 * If the selection runs off the end of the string, round it down.
	 * This may cause it to end up empty.
	 */
    
	if ((entryPtr->selectLast >= 0)
		&& (entryPtr->text[entryPtr->selectLast] == 0)) {
	    entryPtr->selectLast -= 1;
	    if (entryPtr->selectLast < 0) {
		entryPtr->selectFirst = -1;
	    }
	}
    }

    /*
     * Set the caret (if necessary).  If the selection is being set,
     * then the caret goes at the left of the selection.
     */

    if (dragCaret) {
	int newCaret;

	if (dragSelection) {
	    newCaret = entryPtr->selectFirst;
	} else {
	    newCaret = index;
	}
	if (newCaret < 0) {
	    newCaret = 0;
	}
	if (newCaret != entryPtr->caret) {
	    EntryEraseCaret(entryPtr);
	    entryPtr->caret = newCaret;
	}
	Sx_Focus(entryPtr->display, entryPtr->w);
    }

    /*
     * Compute how much to redisplay.  Don't redisplay the part of
     * the selection that was selected before.
     */
    
    if (oldFirst >= 0) {
	if (oldFirst < entryPtr->selectFirst) {
	    tmp = oldLast;
	    if (tmp >= entryPtr->selectFirst) {
		tmp = entryPtr->selectFirst-1;
	    }
	    EntryEraseCaret(entryPtr);
	    EntryRedisplay(entryPtr, oldFirst, tmp, 0);
	}
	if  (oldLast > entryPtr->selectLast) {
	    tmp = oldFirst;
	    if (tmp <= entryPtr->selectLast) {
		tmp = entryPtr->selectLast + 1;
	    }
	    EntryEraseCaret(entryPtr);
	    EntryRedisplay(entryPtr, tmp, oldLast, 0);
	}
    }
    if (entryPtr->selectFirst >= 0) {
	if (entryPtr->selectFirst < oldFirst) {
	    tmp = entryPtr->selectLast;
	    if (tmp >= oldFirst) {
		tmp = oldFirst-1;
	    }
	    EntryEraseCaret(entryPtr);
	    EntryRedisplay(entryPtr, entryPtr->selectFirst, tmp, 0);
	}
	if (entryPtr->selectLast > oldLast) {
	    tmp = entryPtr->selectFirst;
	    if (tmp <= oldLast) {
		tmp = oldLast+1;
	    }
	    EntryEraseCaret(entryPtr);
	    EntryRedisplay(entryPtr, tmp, entryPtr->selectLast, 0);
	}
    }
    EntryDrawCaret(entryPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * EntryKeyProc --
 *
 *	This procedure is invoked by the Sx dispatcher whenever a
 *	key is pressed in an entry window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The text in the window is modified.
 *
 *----------------------------------------------------------------------
 */

static void
EntryKeyProc(entryPtr, eventPtr)
    register Entry *entryPtr;		/* Entry for which event occurred. */
    XKeyEvent *eventPtr;		/* Event that occurred. */
{
    char keyString[20], *p;
    char insert[2];
    int numBytes, i;

    /*
     * Convert the weird raw key value to an ASCII string and process.
     */

    numBytes = XLookupString(eventPtr, keyString, 20, (KeySym *) NULL,
	    (XComposeStatus *) NULL);
    EntryEraseCaret(entryPtr);
    for (p = keyString; numBytes > 0; numBytes--, p++) {
	if (isascii(*p) && isprint(*p)) {
	    insert[0] = *p;
	    insert[1] = 0;
	    EntryInsert(entryPtr, insert, entryPtr->caret);
	} else {
	    for (i = 0; i < numKeystrokes; i++) {
		if (*(bindings[i]) == *p) {
		    break;
		}
	    }
	    if (i > numKeystrokes) {
		continue;
	    }
	    switch (i) {
		case 0:
		case 1:	{		/* Backspace over character. */
		    if (entryPtr->caret != 0) {
			EntryDelete(entryPtr, entryPtr->caret-1,
				entryPtr->caret-1);
		    }
		    break;
		}

		case 2:	{		/* Backspace over word. */
		    int seenNonSpace = 0;
		    for (i = entryPtr->caret-1; i >= 0; i--) {
			if (isspace(entryPtr->text[i])) {
			    if (seenNonSpace) {
				break;
			    }
			} else {
			    seenNonSpace = 1;
			}
		    }
		    EntryDelete(entryPtr, i+1, entryPtr->caret-1);
		    break;
		}

		case 3: {		/* Delete everything in entry. */
		    if (entryPtr->caret != 0) {
			EntryDelete(entryPtr, 0,
				strlen(entryPtr->text) - 1);
		    }
		    break;
		}

		case 4: {		/* Copy selection. */
#define MAX_AT_ONCE 50
		    char selection[MAX_AT_ONCE+1], format[SX_FORMAT_SIZE];
		    int bytesThisTime, offset;
		    for (offset = 0; entryPtr->caret < entryPtr->textSize;
			    offset += bytesThisTime) {
			bytesThisTime = Sx_SelectionGet(entryPtr->display,
				"text", offset, MAX_AT_ONCE, selection,
				format);
			if ((bytesThisTime <= 0)
				|| (strcmp(format, "text") != 0)) {
			    break;
			}
			selection[bytesThisTime] = 0;
			EntryInsert(entryPtr, selection, entryPtr->caret);
		    }
		    break;
		}

		case 5:	{		/* Delete selection. */
		    if (entryPtr->selectFirst >= 0) {
			EntryDelete(entryPtr, entryPtr->selectFirst,
				entryPtr->selectLast);
		    }
		    break;
		}
	    }
	}
    }
    EntryDrawCaret(entryPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * EntryRedisplay --
 *
 *	This procedure is called to redisplay part or all of the
 *	variable portion of an entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The characters indexed first through last from the typed-in
 *	part of the entry are redisplayed.  The entry's label is NOT
 *	redisplayed.
 *
 *----------------------------------------------------------------------
 */

static void
EntryRedisplay(entryPtr, first, last, drawLabel)
    register Entry *entryPtr;		/* What to redisplay. */
    int first, last;			/* Range of characters to redisplay. */
    int drawLabel;			/* 1 means redraw label too, 0
					 * means don't redraw label. */
{
    int x, next;

    entryPtr->flags |= CARET_OFF;

    if (drawLabel && (entryPtr->label != NULL)) {
	XDrawString(entryPtr->display, entryPtr->w, entryPtr->gc,
		LEFT_MARGIN, entryPtr->y, entryPtr->label,
		strlen(entryPtr->label));
    }

    /*
     * Compute the starting location.
     */

    x = entryPtr->x + XTextWidth(entryPtr->fontPtr, entryPtr->text, first);

    /*
     * Display the text in up to three chunks:  one chunk that is
     * to the left of the selected text, one chunk that is highlighted,
     * and one chunk that is to the right of the selected text.
     */
    
    if (entryPtr->selectFirst > first) {
	next = entryPtr->selectFirst;
	if (next > last) {
	    next = last+1;
	}
	XDrawImageString(entryPtr->display, entryPtr->w, entryPtr->gc,
		x, entryPtr->y, &entryPtr->text[first], next-first);
	x += XTextWidth(entryPtr->fontPtr, &entryPtr->text[first], next-first);
	first = next;
    }

    if (entryPtr->selectLast >= first) {
	next = entryPtr->selectLast + 1;
	if (next > last) {
	    next = last+1;
	}
	XSetForeground(entryPtr->display, entryPtr->gc, entryPtr->background);
	XSetBackground(entryPtr->display, entryPtr->gc, entryPtr->foreground);
	XDrawImageString(entryPtr->display, entryPtr->w, entryPtr->gc,
		x, entryPtr->y, &entryPtr->text[first], next-first);
	XSetForeground(entryPtr->display, entryPtr->gc, entryPtr->foreground);
	XSetBackground(entryPtr->display, entryPtr->gc, entryPtr->background);
	x += XTextWidth(entryPtr->fontPtr, &entryPtr->text[first],
		next-first);
	first = next;
    }

    if (first <= last) {
	XDrawImageString(entryPtr->display, entryPtr->w, entryPtr->gc,
		x, entryPtr->y, &entryPtr->text[first],
		last + 1 - first);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryInsert --
 *
 *	Insert new characters into the middle of an entry, and
 *	redisplay them.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The characters in string are inserted into the entry just
 *	before the "before"th character.  The entry will be
 *	truncated if necessary to keep it from overflowing its
 *	allotted storage area.
 *
 *----------------------------------------------------------------------
 */

static void
EntryInsert(entryPtr, string, before)
    register Entry *entryPtr;		/* Entry to be modified. */
    char *string;			/* What to insert. */
    int before;				/* Which character to insert it in
					 * front of. */
{
    int insertLength, suffixLength, spaceLeft;
    register char *src, *dst;
    char *insert;

    /*
     * This code is a bit tricky because of the space limitations on text.
     * Compute how much space is left, then truncate the resulting new
     * string if necessary to make it fit.  This can involve chopping
     * characters from the old string, or even ignoring some of the
     * characters from the insert string.
     */
    
    insert = &entryPtr->text[before];
    suffixLength = strlen(insert);
    insertLength = strlen(string);
    spaceLeft = entryPtr->textSize - (before + insertLength + suffixLength);
    if (spaceLeft < 0) {
	suffixLength += spaceLeft;
	spaceLeft = 0;
	if (suffixLength < 0) {
	    insertLength += suffixLength;
	    suffixLength = 0;
	    if (insertLength == 0) {
		return;
	    }
	}
    }

    /*
     * Move the tail of the current text to its new location, then
     * copy the inserted text into the gap.
     */

    dst = &entryPtr->text[entryPtr->textSize - spaceLeft];
    *dst = 0;
    dst--;
    for (src = dst - insertLength; src >= insert; src--, dst--) {
	*dst = *src;
    }
    (void) strncpy(insert, string, insertLength);

    /*
     * Modify the selection and caret, if necessary, to keep their
     * same positions relative to the text.
     */

    if (entryPtr->selectFirst >= before) {
	entryPtr->selectFirst += insertLength;
	if (entryPtr->selectFirst >= entryPtr->textSize) {
	    entryPtr->selectFirst = entryPtr->selectLast = -1;
	}
    }
    if (entryPtr->selectLast >= before) {
	entryPtr->selectLast += insertLength;
	if (entryPtr->selectLast >= entryPtr->textSize) {
	    entryPtr->selectLast = entryPtr->textSize - 1;
	}
    }
    if (entryPtr->caret >= before) {
	entryPtr->caret += insertLength;
	if (entryPtr->caret > entryPtr->textSize) {
	    entryPtr->caret = entryPtr->textSize;
	}
    }
    EntryRedisplay(entryPtr, before, before + insertLength + suffixLength - 1,
	    0);
}

/*
 *----------------------------------------------------------------------
 *
 * EntryDelete --
 *
 *	Delete one or more characters from an entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Characters indexed first to last are deleted from the text
 *	of entry.  The screen is updated to reflect the change.
 *
 *----------------------------------------------------------------------
 */

static void
EntryDelete(entryPtr, first, last)
    register Entry *entryPtr;		/* Entry to be modified. */
    int first, last;			/* Range of characters to delete. */
{
    int x;

    /*
     * Copy the tail of the string down over the deleted part, and
     * update the selection and caret, if any.
     */

    (void) strcpy(&entryPtr->text[first], &entryPtr->text[last+1]);
    if (entryPtr->selectFirst >= first) {
	if (entryPtr->selectFirst > last) {
	    entryPtr->selectFirst -= last+1-first;
	} else {
	    entryPtr->selectFirst = first;
	}
	if (entryPtr->text[entryPtr->selectFirst] == 0) {
	    entryPtr->selectFirst = entryPtr->selectLast = -1;
	}
    }
    if (entryPtr->selectLast >= first) {
	if (entryPtr->selectLast > last) {
	    entryPtr->selectLast -= last+1-first;
	} else {
	    entryPtr->selectLast = first-1;
	}
	if (entryPtr->selectLast < entryPtr->selectFirst) {
	    entryPtr->selectFirst = entryPtr->selectLast = -1;
	}
    }
    if (entryPtr->caret > first) {
	if (entryPtr->caret > last) {
	    entryPtr->caret -= last+1-first;
	} else {
	    entryPtr->caret = first;
	}
    }

    /*
     * Redisplay the entry starting at the first byte deleted,
     * and also clear the area where the tail of the string
     * used to be displayed.
     */
    
    EntryRedisplay(entryPtr, first, strlen(entryPtr->text) - 1, 0);
    x = entryPtr->x + XTextWidth(entryPtr->fontPtr, entryPtr->text,
	    strlen(entryPtr->text));
    XClearArea(entryPtr->display, entryPtr->w, x, 0, entryPtr->width - x,
	    entryPtr->height, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * EntryFindChar --
 *
 *	Determine which character lies at a given position in an
 *	entry's window.
 *
 * Results:
 *	The result is the index of the closest character in entry's
 *	text to the x-position given by x.  If entryPtr's text is
 *	empty, or if x is to the left of all the text, then 0 is
 *	returned.  If x is to the right of all the text, then the
 *	index of the terminating NULL character in the string is
 *	returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
EntryFindChar(entryPtr, x)
    register Entry *entryPtr;		/* Entry of interest. */
    int x;				/* Location in entryPtr's window. */
{
    register char *p, c;
    int curX, width;
    register XFontStruct *fontPtr = entryPtr->fontPtr;

    if ((fontPtr->min_byte1 != 0) || (fontPtr->max_byte1 != 0)) {
	Sx_Panic(entryPtr->display,
		"EntryFindChar got font with too many characters.");
    }
    for (curX = entryPtr->x, p = entryPtr->text, c = *p; c != 0; p++, c = *p) {
	if (fontPtr->per_char == NULL) {
	    width = fontPtr->max_bounds.width;
	} else if ((c >= fontPtr->min_char_or_byte2)
		&& (c <= fontPtr->max_char_or_byte2)) {
	    width = fontPtr->per_char[c-fontPtr->min_char_or_byte2].width;
	} else {
	    width = fontPtr->min_bounds.width;
	}
	curX += width;
	if (curX > x) {
	    break;
	}
    }
    return p - entryPtr->text;
}

/*
 *----------------------------------------------------------------------
 *
 * EntrySelGet --
 *
 *	Called by the Sx selection package when someone wants to know
 *	what's selected.
 *
 * Results:
 *	See the documentation for Sx_SelectionGet.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
    /* ARGSUSED */
static int
EntrySelGet(entryPtr, desiredFormat, firstByte, numBytes, selectionPtr,
	formatPtr)
    register Entry *entryPtr;		/* Entry that contains selection. */
    char *desiredFormat;		/* Desired format of entry.  This
					 * procedure can only handle text, so
					 * this parameter is ignored. */
    int firstByte;			/* Index of first desired byte. */
    int numBytes;			/* Max no. of bytes to return. */
    char *selectionPtr;			/* Store bytes of selection here. */
    char *formatPtr;			/* Store format of selection here. */
{
    int bytesAvailable;

    if (entryPtr->selectFirst < 0) {
	return -1;
    }
    (void) strcpy(formatPtr, "text");
    bytesAvailable = entryPtr->selectLast + 1
	    - (entryPtr->selectFirst + firstByte);
    if (numBytes > bytesAvailable) {
	numBytes = bytesAvailable;
	if (numBytes <= 0) {
	    return 0;
	}
    }
    (void) strncpy(selectionPtr,
	    &entryPtr->text[entryPtr->selectFirst + firstByte],
	    numBytes);
    return numBytes;
}

/*
 *----------------------------------------------------------------------
 *
 * EntrySelChanged --
 *
 *	Called by Sx whenever the selection is changed away from
 *	an entry window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is unhighlighted, and marked as not being in
 *	this window.
 *
 *----------------------------------------------------------------------
 */

static void
EntrySelChanged(entryPtr)
    register Entry *entryPtr;		/* Entry that lost the selection. */
{
    int oldFirst, oldLast;

    oldFirst = entryPtr->selectFirst;
    oldLast = entryPtr->selectLast;
    entryPtr->selectFirst = entryPtr->selectLast = -1;
    
    if (oldFirst >= 0) {
	EntryEraseCaret(entryPtr);
	EntryRedisplay(entryPtr, oldFirst, oldLast, 0);
	EntryDrawCaret(entryPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryDrawCaret --
 *
 *	Display the caret in an entry window.  This procedure is
 *	typically called after anything happened that might have
 *	caused the caret to be erased.  If it was erased, then
 *	this procedure redraws it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The caret is redrawn, unless it's already visible.
 *
 *----------------------------------------------------------------------
 */

static void
EntryDrawCaret(entryPtr)
    register Entry *entryPtr;	/* Where to draw caret. */
{
    XGCValues gcValues;

    if ((entryPtr->flags & (CARET_OFF|FOCUS_WINDOW))
	    != (CARET_OFF|FOCUS_WINDOW)) {
	return;
    }
    entryPtr->flags &= ~CARET_OFF;

    /*
     * Compute where to draw the caret.
     */
    
    entryPtr->caretX = XTextWidth(entryPtr->fontPtr, entryPtr->text,
	    entryPtr->caret) + entryPtr->x - caret_x_hot;
    entryPtr->caretY = entryPtr->y - caret_y_hot;

    /*
     * White out a mask area, then blacken the caret area.
     */

    gcValues.foreground = entryPtr->background;
    gcValues.ts_x_origin = entryPtr->caretX;
    gcValues.ts_y_origin = entryPtr->caretY;
    gcValues.stipple = caretMask;
    gcValues.fill_style = FillStippled;
    XChangeGC(entryPtr->display, entryPtr->gc, GCForeground|GCTileStipXOrigin
	    |GCTileStipYOrigin|GCStipple|GCFillStyle, &gcValues);
    XFillRectangle(entryPtr->display, entryPtr->w, entryPtr->gc,
	    entryPtr->caretX, entryPtr->caretY, caret_width, caret_height);
    gcValues.foreground = entryPtr->foreground;
    gcValues.stipple = caret;
    XChangeGC(entryPtr->display, entryPtr->gc, GCForeground|GCStipple,
	    &gcValues);
    XFillRectangle(entryPtr->display, entryPtr->w, entryPtr->gc,
	    entryPtr->caretX, entryPtr->caretY, caret_width, caret_height);
    XSetFillStyle(entryPtr->display, entryPtr->gc, FillSolid);
/*
    gcValues.foreground = entryPtr->background;
    gcValues.clip_x_origin = entryPtr->caretX;
    gcValues.clip_y_origin = entryPtr->caretY;
    gcValues.clip_mask= caretMask;
    XChangeGC(entryPtr->display, entryPtr->gc, GCForeground|GCClipXOrigin
	    |GCClipYOrigin|GCClipMask, &gcValues);
    XFillRectangle(entryPtr->display, entryPtr->w, entryPtr->gc,
	    entryPtr->caretX, entryPtr->caretY, caret_width, caret_height);
    gcValues.foreground = entryPtr->foreground;
    gcValues.clip_mask = caret;
    XChangeGC(entryPtr->display, entryPtr->gc, GCForeground|GCClipMask,
	    &gcValues);
    XFillRectangle(entryPtr->display, entryPtr->w, entryPtr->gc,
	    entryPtr->caretX, entryPtr->caretY, caret_width, caret_height);
    XSetClipMask(entryPtr->display, entryPtr->gc, None);
*/
}

/*
 *----------------------------------------------------------------------
 *
 * EntryEraseCaret --
 *
 *	Erase the caret from the given entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The caret is no longer displayed in entryPtr's window. If
 *	it already wasn't displayed, then nothing additional
 *	happens.
 *
 *----------------------------------------------------------------------
 */

static void
EntryEraseCaret(entryPtr)
    register Entry *entryPtr;
{
    int first, last;

    if (entryPtr->flags & CARET_OFF) {
	return;
    }
    entryPtr->flags |= CARET_OFF;

    /*
     * Erase the area of the caret.
     */

    XClearArea(entryPtr->display, entryPtr->w, entryPtr->caretX,
	    entryPtr->caretY, caret_width, caret_height, 0);

    /*
     * Display a few characters on either side of the caret.  The
     * first and last positions in the text have to be handled
     * specially.
     */
    
    if ((entryPtr->caret == 0) && (entryPtr->label != NULL)) {
	XDrawImageString(entryPtr->display, entryPtr->w, entryPtr->gc,
		LEFT_MARGIN, entryPtr->y, entryPtr->label,
		strlen(entryPtr->label));
    }
    first = entryPtr->caret-2;
    if (first < 0) {
	first = 0;
    }
    if (entryPtr->text[entryPtr->caret] == 0) {
	entryPtr->text[entryPtr->caret] = ' ';
	EntryRedisplay(entryPtr, first, entryPtr->caret, 0);
	entryPtr->text[entryPtr->caret] = 0;
    } else {
	last = strlen(entryPtr->text) - 1;
	if (last > entryPtr->caret+1) {
	    last = entryPtr->caret+1;
	}
	EntryRedisplay(entryPtr, first, last, 0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryInit --
 *
 *	Initialize this module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Bitmaps and cursors get allocated.
 *
 *----------------------------------------------------------------------
 */

static void
EntryInit(display)
    Display *display;
{
    static int initialized = 0;
    static XColor black = {0, 0, 0, 0, 0};
    static XColor white = {0, ~0, ~0, ~0, 0};
    Pixmap source, mask;
    Window root;
    char *tmp;
    int i;

    if (initialized) {
	return;
    }
    initialized = 1;

    entryContext = XUniqueContext();
    root = RootWindow(display, DefaultScreen(display));

    source = XCreateBitmapFromData(display, root, ptr_bits,
	    ptr_width, ptr_height);
    mask = XCreateBitmapFromData(display, root, ptrMask_bits,
	    ptrMask_width, ptrMask_height);
    cursor = XCreatePixmapCursor(display, source, mask, &black, &white,
	    ptr_x_hot, ptr_y_hot);
    XFreePixmap(display, source);

    caret = XCreateBitmapFromData(display, root, caret_bits,
	    caret_width, caret_height);
    caretMask = XCreateBitmapFromData(display, root, caretMask_bits,
	    caretMask_width, caretMask_height);

    for (i = 0; i < numKeystrokes; i++) {
	tmp = XGetDefault(display, "sx", defaultNames[i]);
	if (tmp != NULL) {
	    bindings[i] = (char *)
		    malloc((unsigned) (strlen(tmp) + 1));
	    (void) strcpy(bindings[i], tmp);
	}
	EntryCvtBinding(bindings[i]);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EntryCvtBinding --
 *
 *	Convert an ASCII string to internal format, replacing backslash
 *	sequences in it with control- and meta-characters.
 *
 * Results:
 *	The string at src is overwritten by a copy of itself, except
 *	that backslashes and the characters immediately following them
 *	are replaced as indicated by the following table:
 *		\b  - backspace
 *		\e  - ESCAPE
 *		\n  - newline
 *		\t  - tab
 *		\Cx - control-x (for any ASCII x)
 *		\Mx - x + 0200 (for any ASCII x)
 *		\?  - if ? is any sequence of octal digits, replace with
 *		      the given octal value.  Otherwise, just use the
 *		      character following the backslash.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
EntryCvtBinding(src)
    register char *src;		/* Pointer to source string. */
{
    register char *dst, c;

    for (dst = src; *src != 0; src++, dst++) {
	c = *src;
	if (c == '\\') {
	    src++;
	    c = *src;
	    if (isdigit(c)) {
		int i;
		i = strtol(src, (char **) NULL, 8);
		c = i;
	    } else if (c == 'n') {
		c = '\n';
	    }  else if (c == 't') {
		c = '\t';
	    } else if (c == 'b') {
		c = '\b';
	    } else if (c == 'e') {
		c = 033;
	    } else if (c == 'C') {
		src++;
		c = *src;
		if (c != 0) {
		    c &= 037;
		}
	    } else if (c == 'M') {
		src++;
		c = *src;
		if (c != 0) {
		    c |= 0200;
		}
	    }
	}
	*dst = c;
    }
    *dst = 0;
}
