/*
 * bltHtText.c --
 *
 *	This module implements an hierarchy widget for the BLT toolkit.
 *
 * Copyright 1998-1999 Lucent Technologies, Inc.
 *
 * 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 the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies or any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies 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 tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *	The "hiertable" widget was created by George A. Howlett.
 */

#include "bltInt.h"

#ifndef NO_HIERTABLE

#include "bltHiertable.h"
#include "bltList.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#define EDITOR_FOCUS	(1<<0)
#define EDITOR_REDRAW	(1<<1)

static void DisplayEditor _ANSI_ARGS_((ClientData clientData));
static void DestroyEditor _ANSI_ARGS_((DestroyData data));

static Tcl_TimerProc BlinkCursorProc;

extern Tk_CustomOption bltDistanceOption;

/*
 * Editor --
 *
 *	This structure is shared by entries when their labels are
 *	edited via the keyboard.  It maintains the location of the
 *	insertion cursor and the text selection for the editted entry.
 *	The structure is shared since we need only one.  The "focus"
 *	entry should be the only entry receiving KeyPress/KeyRelease
 *	events at any time.  Information from the previously editted
 *	entry is overwritten.
 *
 *	Note that all the indices internally are in terms of bytes,
 *	not characters.  This is because UTF-8 strings may encode a
 *	single character into a multi-byte sequence.  To find the
 *	respective character position
 *
 *		n = Tcl_NumUtfChars(string, index);
 *
 *	where n is the resulting character number.
 */
struct EditorRec {
    unsigned int flags;
    Display *display;
    Tk_Window tkwin;		/* Window representing the editing frame. */
    int x, y;			/* Position of window. */
    int width, height;		/* Dimensions of editor window. */

    Hiertable *htabPtr;		/* Widget associated with editor window. */
    int active;			/* Indicates that the frame is active. */
    int exportSelection;

    int insertPos;		/* Position of the cursor within the
				 * array of bytes of the entry's label. */

    Tk_Cursor cursor;		/* X Cursor */
    int cursorX, cursorY;	/* Position of the insertion cursor in the
				 * editor window. */
    short int cursorWidth;	/* Size of the insertion cursor symbol. */
    short int cursorHeight;

    int selAnchor;		/* Fixed end of selection. Used to extend
				 * the selection while maintaining the
				 * other end of the selection. */
    int selFirst;		/* Position of the first character in the
				 * selection. */
    int selLast;		/* Position of the last character in the
				 * selection. */

    int cursorOn;		/* Indicates if the cursor is displayed. */
    int onTime, offTime;	/* Time in milliseconds to wait before
				 * changing the cursor from off-to-on
				 * and on-to-off. Setting offTime to 0 makes
				 * the cursor steady. */
    Tcl_TimerToken timerToken;	/* Handle for a timer event called periodically
				 * to blink the cursor. */
    /* Data-specific fields. */
    Entry *entryPtr;		/* Selected entry */
    Column *columnPtr;		/* Column of entry to be edited */
    char *string;
    TextLayout *layoutPtr;
    Tk_Font font;
    GC gc;

    Tk_3DBorder selBorder;
    int selRelief;
    int selBorderWidth;
    Tk_3DBorder border;
    int relief;
    int borderWidth;
    XColor *selFgColor;		/* Text color of a selected entry. */


};

#define DEF_EDITOR_BACKGROUND		RGB_WHITE
#define DEF_EDITOR_BORDER_WIDTH		STD_BORDERWIDTH
#define DEF_EDITOR_CURSOR		(char *)NULL
#define DEF_EDITOR_EXPORT_SELECTION	"no"
#define DEF_EDITOR_NORMAL_BG_COLOR 	STD_COLOR_NORMAL_BG
#define DEF_EDITOR_NORMAL_FG_MONO	STD_MONO_ACTIVE_FG
#define DEF_EDITOR_RELIEF		"solid"
#define DEF_EDITOR_SELECT_BG_COLOR 	RGB_LIGHTSKYBLUE1
#define DEF_EDITOR_SELECT_BG_MONO  	STD_MONO_SELECT_BG
#define DEF_EDITOR_SELECT_BORDER_WIDTH	"1"
#define DEF_EDITOR_SELECT_FG_COLOR 	STD_COLOR_SELECT_FG
#define DEF_EDITOR_SELECT_FG_MONO  	STD_MONO_SELECT_FG
#define DEF_EDITOR_SELECT_RELIEF	"raised"

/* Editor Procedures */
static Tk_ConfigSpec editorConfigSpecs[] =
{
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_EDITOR_BACKGROUND, Tk_Offset(Editor, border), 0},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_EDITOR_CURSOR, Tk_Offset(Editor, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_EDITOR_BORDER_WIDTH, Tk_Offset(Editor, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
	"ExportSelection", DEF_EDITOR_EXPORT_SELECTION, 
	Tk_Offset(Editor, exportSelection), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_EDITOR_RELIEF, Tk_Offset(Editor, relief), 0},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
	DEF_EDITOR_SELECT_BG_MONO, Tk_Offset(Editor, selBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
	DEF_EDITOR_SELECT_BG_COLOR, Tk_Offset(Editor, selBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
	DEF_EDITOR_SELECT_BORDER_WIDTH, Tk_Offset(Editor, selBorderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
	DEF_EDITOR_SELECT_FG_MONO, Tk_Offset(Editor, selFgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
	DEF_EDITOR_SELECT_FG_COLOR, Tk_Offset(Editor, selFgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief",
	DEF_EDITOR_SELECT_RELIEF, Tk_Offset(Editor, selRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

#ifdef __STDC__
static Tk_LostSelProc EditorLostSelectionProc;
static Tk_SelectionProc EditorSelectionProc;
static Tk_EventProc EditorEventProc;
#endif

/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedraw --
 *
 *	Queues a request to redraw the widget at the next idle point.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets redisplayed.  Right now we don't do selective
 *	redisplays:  the whole window will be redrawn.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyRedraw(editPtr)
    Editor *editPtr;
{
    if ((editPtr->tkwin != NULL) && !(editPtr->flags & EDITOR_REDRAW)) {
	editPtr->flags |= EDITOR_REDRAW;
	Tcl_DoWhenIdle(DisplayEditor, (ClientData)editPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * BlinkCursorProc --
 *
 *	This procedure is called as a timer handler to blink the
 *	insertion cursor off and on.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cursor gets turned on or off, redisplay gets invoked,
 *	and this procedure reschedules itself.
 *
 *----------------------------------------------------------------------
 */
static void
BlinkCursorProc(clientData)
    ClientData clientData;	/* Pointer to record describing entry. */
{
    Editor *editPtr = (Editor *)clientData;
    int interval;

    if (!(editPtr->flags & EDITOR_FOCUS) || (editPtr->offTime == 0)) {
	return;
    }
    if (editPtr->active) {
	editPtr->cursorOn ^= 1;
	interval = (editPtr->cursorOn) ? editPtr->onTime : editPtr->offTime;
	editPtr->timerToken = Tcl_CreateTimerHandler(interval, BlinkCursorProc,
	     (ClientData)editPtr);
	EventuallyRedraw(editPtr);
    }
}

/*
 * --------------------------------------------------------------
 *
 * EditorEventProc --
 *
 * 	This procedure is invoked by the Tk dispatcher for various
 * 	events on hierarchy widgets.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 * --------------------------------------------------------------
 */
static void
EditorEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Editor *editPtr = (Editor *)clientData;

    if (eventPtr->type == Expose) {
	if (eventPtr->xexpose.count == 0) {
	    EventuallyRedraw(editPtr);
	}
    } else if (eventPtr->type == ConfigureNotify) {
	EventuallyRedraw(editPtr);
    } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
	if (eventPtr->xfocus.detail == NotifyInferior) {
	    return;
	}
	if (eventPtr->type == FocusIn) {
	    editPtr->flags |= EDITOR_FOCUS;
	} else {
	    editPtr->flags &= ~EDITOR_FOCUS;
	}
	Tcl_DeleteTimerHandler(editPtr->timerToken);
	if ((editPtr->active) && (editPtr->flags & EDITOR_FOCUS)) {
	    editPtr->cursorOn = TRUE;
	    if (editPtr->offTime != 0) {
		editPtr->timerToken = Tcl_CreateTimerHandler(editPtr->onTime, 
		   BlinkCursorProc, clientData);
	    }
	} else {
	    editPtr->cursorOn = FALSE;
	    editPtr->timerToken = (Tcl_TimerToken) NULL;
	}
	EventuallyRedraw(editPtr);
    } else if (eventPtr->type == DestroyNotify) {
	if (editPtr->tkwin != NULL) {
	    editPtr->tkwin = NULL;
	}
	if (editPtr->flags & EDITOR_REDRAW) {
	    Tcl_CancelIdleCall(DisplayEditor, (ClientData)editPtr);
	}
	if (editPtr->timerToken != NULL) {
	    Tcl_DeleteTimerHandler(editPtr->timerToken);
	}
	Tcl_EventuallyFree((ClientData)editPtr, DestroyEditor);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EditorLostSelectionProc --
 *
 *	This procedure is called back by Tk when the selection is
 *	grabbed away from a Text widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The existing selection is unhighlighted, and the window is
 *	marked as not containing a selection.
 *
 *----------------------------------------------------------------------
 */
static void
EditorLostSelectionProc(clientData)
    ClientData clientData;	/* Information about Text widget. */
{
    Editor *editPtr = (Editor *)clientData;

    if ((editPtr->selFirst >= 0) && (editPtr->exportSelection)) {
	editPtr->selFirst = editPtr->selLast = -1;
	EventuallyRedraw(editPtr);
    }
}

static int
PointerToIndex(editPtr, x, y)
    Editor *editPtr;
    int x, y;
{
    TextLayout *layoutPtr;
    Tk_FontMetrics fontMetrics;
    TextSegment *segPtr;
    int nBytes;
    register int i;
    int total;

    if ((editPtr->string == NULL) || (editPtr->string[0] == '\0')) {
	return 0;
    }
    x -= editPtr->selBorderWidth;
    y -= editPtr->selBorderWidth;

    layoutPtr = editPtr->layoutPtr;

    /* Bound the y-coordinate within the window. */
    if (y < 0) {
	y = 0;
    } else if (y >= layoutPtr->height) {
	y = layoutPtr->height - 1;
    }
    /* 
     * Compute the line that contains the y-coordinate. 
     *
     * FIXME: This assumes that segments are distributed 
     *	     line-by-line.  This may change in the future.
     */
    Tk_GetFontMetrics(editPtr->font, &fontMetrics);
    segPtr = layoutPtr->segArr;
    total = 0;
    for (i = (y / fontMetrics.linespace); i > 0; i--) {
	total += segPtr->count;
	segPtr++;
    }
    if (x < 0) {
	nBytes = 0;
    } else if (x >= layoutPtr->width) {
	nBytes = segPtr->count;
    } else {
	int newX;

	/* Find the character underneath the pointer. */
	nBytes = Tk_MeasureChars(editPtr->font, segPtr->text, segPtr->count, 
		 x, 0, &newX);
	if ((newX < x) && (nBytes < segPtr->count)) {
	    float fract;
	    int length, charSize;
	    char *next;

	    next = segPtr->text + nBytes;
#if HAVE_UTF
	    {
		Tcl_UniChar dummy;

		length = Tcl_UtfToUniChar(next, &dummy);
	    }
#else
	    length = 1;
#endif
	    charSize = Tk_TextWidth(editPtr->font, next, length);
	    fract = ((float)(x - newX) / (float)charSize);
	    if (ROUND(fract)) {
		nBytes += length;
	    }
	}
    }
    return nBytes + total;
}

static int
IndexToPointer(editPtr)
    Editor *editPtr;
{
    int x, y;
    int maxLines;
    TextLayout *layoutPtr;
    Tk_FontMetrics fontMetrics;
    int nBytes;
    int sum;
    TextSegment *segPtr;
    register int i;

    layoutPtr = editPtr->layoutPtr;
    Tk_GetFontMetrics(editPtr->font, &fontMetrics);
    maxLines = (layoutPtr->height / fontMetrics.linespace) - 1;

    nBytes = sum = 0;
    x = y = 0;
    segPtr = layoutPtr->segArr;
    for (i = 0; i <= maxLines; i++) {
	/* Total the number of bytes on each line.  Include newlines. */
	nBytes = segPtr->count + 1;
	if ((sum + nBytes) > editPtr->insertPos) {
	    x += Tk_TextWidth(editPtr->font, segPtr->text, 
		editPtr->insertPos - sum);
	    break;
	}
	y += fontMetrics.linespace;
	sum += nBytes;
	segPtr++;
    }
    editPtr->cursorX = x;
    editPtr->cursorY = y;
    editPtr->cursorHeight = fontMetrics.linespace;
    editPtr->cursorWidth = 3;
    return TCL_OK;
}

static void
UpdateLayout(editPtr)
    Editor *editPtr;
{
    TextStyle style;
    int width, height;

    if (editPtr->layoutPtr == NULL) {
	free((char *)editPtr->layoutPtr);
    }
    Blt_InitTextStyle(&style);
    style.anchor = TK_ANCHOR_NW;
    style.justify = TK_JUSTIFY_LEFT;
    style.font = editPtr->font;
    editPtr->layoutPtr = Blt_GetTextLayout(editPtr->string, &style);
    width = editPtr->layoutPtr->width;
    if (width < editPtr->columnPtr->width) {
	width = editPtr->columnPtr->width;
    }
    height = editPtr->layoutPtr->height;
    if (height < 1) {
	Tk_FontMetrics fontMetrics;
	
	Tk_GetFontMetrics(editPtr->font, &fontMetrics);
	height = fontMetrics.linespace;
    }
    editPtr->width = width + 2 * editPtr->borderWidth;
    editPtr->height = height + 2 * editPtr->borderWidth;
    IndexToPointer(editPtr);
    Tk_MoveResizeWindow(editPtr->tkwin, editPtr->x, editPtr->y, 
		editPtr->width, editPtr->height);
}

static void
InsertText(editPtr, insertText, insertPos, nBytes)
    Editor *editPtr;
    char *insertText;
    int insertPos;
    int nBytes;
{
    int oldSize, newSize;
    char *oldText, *newText;

    oldText = editPtr->string;
    oldSize = strlen(oldText);
    newSize = oldSize + nBytes;
    newText = (char *)malloc(sizeof(char) * (newSize + 1));
    if (insertPos == oldSize) {	/* Append */
	strcpy(newText, oldText);
	strcat(newText, insertText);
    } else if (insertPos == 0) {/* Prepend */
	strcpy(newText, insertText);
	strcat(newText, oldText);
    } else {			/* Insert into existing. */
	char *p;
	
	p = newText;
	strncpy(p, oldText, insertPos);
	p += insertPos;
	strcpy(p, insertText);
	p += nBytes;
	strcpy(p, oldText + insertPos);
    }

    /*
     * All indices from the start of the insertion to the end of the
     * string need to be updated.  Simply move the indices down by the
     * number of characters added.
     */
    if (editPtr->selFirst >= insertPos) {
	editPtr->selFirst += nBytes;
    }
    if (editPtr->selLast > insertPos) {
	editPtr->selLast += nBytes;
    }
    if ((editPtr->selAnchor > insertPos) || (editPtr->selFirst >= insertPos)) {
	editPtr->selAnchor += nBytes;
    }
    if (editPtr->string != NULL) {
	free((char *)editPtr->string);
    }
    editPtr->string = newText;
    editPtr->insertPos = insertPos + nBytes;
    UpdateLayout(editPtr);
}

static int
DeleteText(editPtr, firstPos, lastPos)
    Editor *editPtr;
    int firstPos, lastPos;
{
    char *oldText, *newText;
    int oldSize, newSize;
    int nBytes;
    char *p;

    oldText = editPtr->string;
    if (firstPos > lastPos) {
	return TCL_OK;
    }
    lastPos++;			/* Now is the position after the last
				 * character. */

    nBytes = lastPos - firstPos;
    oldSize = strlen(oldText);
    newSize = oldSize - nBytes;
    newText = (char *)malloc(sizeof(char) * (newSize + 1));
    p = newText;
    strncpy(p, oldText, firstPos);
    p += firstPos;
    strcpy(p, oldText + lastPos);
    free(oldText);

    /*
     * Since deleting characters compacts the character array, we need to
     * update the various character indices according.  It depends where
     * the index occurs in relation to range of deleted characters:
     *
     *	 before		Ignore.
     *   within		Move the index back to the start of the deletion.
     *	 after		Subtract off the deleted number of characters,
     *			since the array has been compressed by that
     *			many characters.
     *
     */
    if (editPtr->selFirst >= firstPos) {
	if (editPtr->selFirst >= lastPos) {
	    editPtr->selFirst -= nBytes;
	} else {
	    editPtr->selFirst = firstPos;
	}
    }
    if (editPtr->selLast >= firstPos) {
	if (editPtr->selLast >= lastPos) {
	    editPtr->selLast -= nBytes;
	} else {
	    editPtr->selLast = firstPos;
	}
    }
    if (editPtr->selLast <= editPtr->selFirst) {
	editPtr->selFirst = editPtr->selLast = -1; /* Cut away the entire
						    * selection. */ 
    }
    if (editPtr->selAnchor >= firstPos) {
	if (editPtr->selAnchor >= lastPos) {
	    editPtr->selAnchor -= nBytes;
	} else {
	    editPtr->selAnchor = firstPos;
	}
    }
    if (editPtr->insertPos >= firstPos) {
	if (editPtr->insertPos >= lastPos) {
	    editPtr->insertPos -= nBytes;
	} else {
	    editPtr->insertPos = firstPos;
	}
    }
    editPtr->string = newText;
    UpdateLayout(editPtr);
    EventuallyRedraw(editPtr);
    return TCL_OK;
}

static int
AcquireText(editPtr, entryPtr, columnPtr)
    Editor *editPtr;
    Entry *entryPtr;
    Column *columnPtr;
{
    Hiertable *htabPtr = editPtr->htabPtr;
    int sx, sy;
    TextStyle style;
    char *string;
    GC gc;

    Blt_InitTextStyle(&style);
    style.anchor = TK_ANCHOR_NW;
    style.justify = TK_JUSTIFY_LEFT;
    if (columnPtr == htabPtr->treeColumnPtr) {
	int level;

	level = DEPTH(htabPtr, entryPtr->node);
	sx = SCREENX(htabPtr, entryPtr->worldX);
	sy = SCREENY(htabPtr, entryPtr->worldY);
	sx += ICONWIDTH(level) + ICONWIDTH(level + 1) + 4;
	style.font = GETFONT(htabPtr->treeColumnPtr->font, entryPtr->font);
	string = GETLABEL(entryPtr);
	gc = columnPtr->gc;
    } else {
	string = Blt_HtGetData(entryPtr, columnPtr->keyUid);
	sx = SCREENX(htabPtr, columnPtr->worldX);
	sy = SCREENY(htabPtr, entryPtr->worldY);
	style.font = columnPtr->font;
	gc = (entryPtr->gc == NULL) ? htabPtr->treeColumnPtr->gc : entryPtr->gc;
    }
    if (editPtr->layoutPtr != NULL) {
	free((char *)editPtr->layoutPtr);
    }
    if (editPtr->string != NULL) {
	free((char *)editPtr->string);
    }
    if (string == NULL) {
	string = "";
    }
    editPtr->entryPtr = entryPtr;
    editPtr->columnPtr = columnPtr;
    editPtr->x = sx - editPtr->borderWidth;
    editPtr->y = sy - editPtr->borderWidth;
    editPtr->string = strdup(string);
    editPtr->gc = gc;
    editPtr->font = style.font;
    editPtr->selFirst = editPtr->selLast = -1;
    UpdateLayout(editPtr);
    Tk_MapWindow(editPtr->tkwin);
    EventuallyRedraw(editPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * GetTextIndex --
 *
 *	Parse an index into an entry and return either its value
 *	or an error.
 *
 * Results:
 *	A standard Tcl result.  If all went well, then *indexPtr is
 *	filled in with the character index (into entryPtr) corresponding to
 *	string.  The index value is guaranteed to lie between 0 and
 *	the number of characters in the string, inclusive.  If an
 *	error occurs then an error message is left in the interp's result.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */
static int
GetTextIndex(editPtr, string, indexPtr)
    Editor *editPtr;
    char *string;
    int *indexPtr;
{
    Tcl_Interp *interp = editPtr->htabPtr->interp;
    unsigned int textPos;
    char c;

    c = string[0];
    if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
	textPos = editPtr->selAnchor;
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
	textPos = strlen(editPtr->string);
    } else if ((c == 'i') && (strcmp(string, "insert") == 0)) {
	textPos = editPtr->insertPos;
    } else if ((c == 'n') && (strcmp(string, "next") == 0)) {
	textPos = editPtr->insertPos;
	if (textPos < strlen(editPtr->string)) {
	    textPos++;
	}
    } else if ((c == 'l') && (strcmp(string, "last") == 0)) {
	textPos = editPtr->insertPos;
	if (textPos > 0) {
	    textPos--;
	}
    } else if ((c == 's') && (strcmp(string, "sel.first") == 0)) {
	if (editPtr->selFirst < 0) {
	    Tcl_AppendResult(interp, "nothing is selected", (char *)NULL);
	    return TCL_ERROR;
	}
	textPos = editPtr->selFirst;
    } else if ((c == 's') && (strcmp(string, "sel.last") == 0)) {
	if (editPtr->selLast < 0) {
	    Tcl_AppendResult(interp, "nothing is selected", (char *)NULL);
	    return TCL_ERROR;
	}
	textPos = editPtr->selLast;
    } else if (c == '@') {
	int x, y;

	if (Blt_GetXY(interp, editPtr->tkwin, string, &x, &y) != TCL_OK) {
	    return TCL_ERROR;
	}
	textPos = PointerToIndex(editPtr, x, y);
    } else if (isdigit((int)c)) {
	int number;
	int maxChars;

	if (Tcl_GetInt(interp, string, &number) != TCL_OK) {
	    return TCL_ERROR;
	}
	/* Don't allow the index to point outside the label. */
	maxChars = Tcl_NumUtfChars(editPtr->string, -1);
	if (number < 0) {
	    textPos = 0;
	} else if (number > maxChars) {
	    textPos = strlen(editPtr->string);
	} else {
	    textPos = Tcl_UtfAtIndex(editPtr->string, number) - 
		editPtr->string;
	}
    } else {
	Tcl_AppendResult(interp, "bad label index \"", string, "\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    *indexPtr = textPos;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectText --
 *
 *	Modify the selection by moving its un-anchored end.  This
 *	could make the selection either larger or smaller.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectText(editPtr, textPos)
    Editor *editPtr;		/* Information about editor. */
    int textPos;		/* Index of element that is to
				 * become the "other" end of the
				 * selection. */
{
    int selFirst, selLast;

    /*
     * Grab the selection if we don't own it already.
     */
    if ((editPtr->exportSelection) && (editPtr->selFirst == -1)) {
	Tk_OwnSelection(editPtr->tkwin, XA_PRIMARY, EditorLostSelectionProc,
	    (ClientData)editPtr);
    }
    /*  If the anchor hasn't been set yet, assume the beginning of the text*/
    if (editPtr->selAnchor < 0) {
	editPtr->selAnchor = 0;
    }
    if (editPtr->selAnchor <= textPos) {
	selFirst = editPtr->selAnchor;
	selLast = textPos;
    } else {
	selFirst = textPos;
	selLast = editPtr->selAnchor;
    }
    if ((editPtr->selFirst != selFirst) || (editPtr->selLast != selLast)) {
	editPtr->selFirst = selFirst;
	editPtr->selLast = selLast;
	EventuallyRedraw(editPtr);
    }
    return TCL_OK;
}



/*
 *----------------------------------------------------------------------
 *
 * EditorSelectionProc --
 *
 *	This procedure is called back by Tk when the selection is
 *	requested by someone.  It returns part or all of the selection
 *	in a buffer provided by the caller.
 *
 * Results:
 *	The return value is the number of non-NULL bytes stored at
 *	buffer.  Buffer is filled (or partially filled) with a
 *	NUL-terminated string containing part or all of the
 *	selection, as given by offset and maxBytes.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static int
EditorSelectionProc(clientData, offset, buffer, maxBytes)
    ClientData clientData;	/* Information about the widget. */
    int offset;			/* Offset within selection of first
				 * character to be returned. */
    char *buffer;		/* Location in which to place
				 * selection. */
    int maxBytes;		/* Maximum number of bytes to place
				 * at buffer, not including terminating
				 * NULL character. */
{
    Editor *editPtr = (Editor *)clientData;
    int size;

    size = strlen(editPtr->string + offset);
    /*
     * Return the string currently in the editor.
     */
    strncpy(buffer, editPtr->string + offset, maxBytes);
    buffer[maxBytes] = '\0';
    return (size > maxBytes) ? maxBytes : size;
}


static void
DestroyEditor(data)
    DestroyData data;
{
    Editor *editPtr = (Editor *)data;

    Tk_FreeOptions(editorConfigSpecs, (char *)editPtr, 
	editPtr->htabPtr->display, 0);

    if (editPtr->string != NULL) {
	free(editPtr->string);
    }
    if (editPtr->layoutPtr != NULL) {
	free((char *)editPtr->layoutPtr);
    }
    if (editPtr->timerToken != NULL) {
	Tcl_DeleteTimerHandler(editPtr->timerToken);
    }
    if (editPtr->tkwin != NULL) {
	Tk_DeleteSelHandler(editPtr->tkwin, XA_PRIMARY, XA_STRING);
    }
    free((char *)editPtr);
}

static void
ConfigureEditor(editPtr)
    Editor *editPtr;
{
#ifdef notdef
    editPtr->width = editPtr->layoutPtr->width + 
	2 * (editPtr->borderWidth + editPtr->highlightWidth);
    editPtr->height = editPtr->layoutPtr->height + 
	2 * (editPtr->borderWidth + editPtr->highlightWidth);
    
    if (Tk_IsMapped(editPtr->tkwin)) {
	if ((editPtr->height != Tk_Height(editPtr->tkwin)) ||
	    (editPtr->width != Tk_Width(editPtr->tkwin))) {
	    Tk_ResizeWindow(editPtr->tkwin, editPtr->width, editPtr->height);
	}
    }
#endif
}

Editor *
Blt_HtCreateEditor(htabPtr)
    Hiertable *htabPtr;
{
    Editor *editPtr;
    Tk_Window tkwin;

    tkwin = Tk_CreateWindow(htabPtr->interp, htabPtr->tkwin, "edit", 
	    (char *)NULL);
    if (tkwin == NULL) {
	return NULL;
    }
    Tk_SetClass(tkwin, "HiertableEditor"); 

    editPtr = (Editor *)calloc(1, sizeof(Editor));
    assert(editPtr);

    editPtr->tkwin = tkwin;
    editPtr->htabPtr = htabPtr;
    editPtr->borderWidth = 1;
    editPtr->relief = TK_RELIEF_SOLID;
    editPtr->selRelief = TK_RELIEF_RAISED;
    editPtr->selBorderWidth = 1;
    editPtr->selAnchor = -1;
    editPtr->selFirst = editPtr->selLast = -1;
    editPtr->onTime = 600;
    editPtr->active = TRUE;
    editPtr->offTime = 300;
#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, (ClientData)editPtr);
#endif
    Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, EditorSelectionProc,
	(ClientData)editPtr, XA_STRING);
    Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask |
	FocusChangeMask, EditorEventProc, (ClientData)editPtr);
    if (Tk_ConfigureWidget(htabPtr->interp, tkwin, editorConfigSpecs, 0, 
	(char **)NULL, (char *)editPtr, 0) != TCL_OK) {
	Tk_DestroyWindow(tkwin);
	return NULL;
    }
    return editPtr;
}

static void
DisplayEditor(clientData)
    ClientData clientData;
{
    Editor *editPtr = (Editor *)clientData;
    Pixmap drawable;
    GC gc;
    register int i;
    int lastX;
    int count, nChars;
    int leftPos, rightPos;
    int selStart, selEnd, selLength;
    int x, y;
    TextSegment *segPtr;
    Tk_FontMetrics fontMetrics;

    editPtr->flags &= ~EDITOR_REDRAW;
    if (!Tk_IsMapped(editPtr->tkwin)) {
	return;
    }
    drawable = Tk_GetPixmap(Tk_Display(editPtr->tkwin), 
	Tk_WindowId(editPtr->tkwin), Tk_Width(editPtr->tkwin), 
	Tk_Height(editPtr->tkwin), Tk_Depth(editPtr->tkwin));

    Tk_Fill3DRectangle(editPtr->tkwin, drawable, editPtr->border, 0, 0,
	Tk_Width(editPtr->tkwin), Tk_Height(editPtr->tkwin), 
	editPtr->borderWidth, editPtr->relief);

    Tk_GetFontMetrics(editPtr->font, &fontMetrics);
    segPtr = editPtr->layoutPtr->segArr;
    count = 0;
    for (i = 0; i < editPtr->layoutPtr->nSegments; i++, segPtr++) {
	leftPos = count;
	count += segPtr->count;
	rightPos = count;
	y = segPtr->y + editPtr->borderWidth + 1;
	x = editPtr->borderWidth + editPtr->columnPtr->pad.side1;
	if ((rightPos < editPtr->selFirst) || (leftPos > editPtr->selLast)) {
	    /* No selected text */
	    Tk_DrawChars(Tk_Display(editPtr->tkwin), drawable, editPtr->gc, 
		editPtr->font, segPtr->text, segPtr->count, x, y);
	    continue;
	}
	/*
	 *  A text segment (with selected text) may have up to 3 regions:
	 *
	 *	1. Text before the start the selection
	 *	2. Selected text itself (drawn in a raised border)
	 *	3. Text following the selection.
	 */

	selStart = leftPos;
	selEnd = rightPos;
	/* First adjust selected region for current line. */
	if (editPtr->selFirst > leftPos) {
	    selStart = editPtr->selFirst;
	}
	if (editPtr->selLast < rightPos) {
	    selEnd = editPtr->selLast;
	}
	selLength = (selEnd - selStart) + 1;
	lastX = x;

	if (selStart > leftPos) { /* Normal text preceding the selection */
	    nChars = (selStart - leftPos);
	    Tk_MeasureChars(editPtr->font, editPtr->string + leftPos,
		    nChars, 10000, DEF_TEXT_FLAGS, &lastX);
	    lastX += x;
	}
	if (selLength > 0) {	/* The selection itself */
	    int width, nextX;

	    Tk_MeasureChars(editPtr->font, segPtr->text + selStart,
		    selLength, 10000, DEF_TEXT_FLAGS, &nextX);
	    nextX += x;
	    width = (nextX - lastX) + 1;
	    Tk_Fill3DRectangle(editPtr->tkwin, drawable, editPtr->selBorder,
		lastX, y - fontMetrics.ascent, width, fontMetrics.linespace, 
		editPtr->selBorderWidth, editPtr->selRelief);
	}
	Tk_DrawChars(Tk_Display(editPtr->tkwin), drawable, editPtr->gc, 
	     editPtr->font, segPtr->text, segPtr->count, x, y);
    }
    gc = DefaultGC(Tk_Display(editPtr->tkwin), Tk_ScreenNumber(editPtr->tkwin));
    if ((editPtr->flags & EDITOR_FOCUS) && (editPtr->cursorOn)) {
	int x1, y1, x2, y2;

	IndexToPointer(editPtr);
	x1 = editPtr->cursorX + editPtr->borderWidth + 1;
	x2 = x1 + 1;
	y1 = editPtr->cursorY + 2;
	y2 = y1 + editPtr->cursorHeight - 1;
	XDrawLine(Tk_Display(editPtr->tkwin), drawable, gc, x1, y1, x1, y2);
	XDrawLine(Tk_Display(editPtr->tkwin), drawable, gc, x1-1, y1, x2, y1);
	XDrawLine(Tk_Display(editPtr->tkwin), drawable, gc, x1-1, y2, x2, y2);
    }
    XCopyArea(Tk_Display(editPtr->tkwin), drawable, Tk_WindowId(editPtr->tkwin),
	gc, 0, 0, Tk_Width(editPtr->tkwin), Tk_Height(editPtr->tkwin), 0, 0);
    Tk_FreePixmap(Tk_Display(editPtr->tkwin), drawable);

}

/*ARGSUSED*/
static int
ApplyOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Hiertable *htabPtr;
    Entry *entryPtr;

    htabPtr = editPtr->htabPtr;
    entryPtr = editPtr->entryPtr;
    if (editPtr->columnPtr == htabPtr->treeColumnPtr) {

	if (entryPtr->labelUid != NULL) {
	    Blt_HtFreeUid(htabPtr, entryPtr->labelUid);
	}
	if (editPtr->string == NULL) {
	    entryPtr->labelUid = Blt_HtGetUid(htabPtr, "");
	} else {
	    entryPtr->labelUid = Blt_HtGetUid(htabPtr, editPtr->string);
	}
    } else {
	Column *columnPtr;
	Tcl_Obj *objPtr;

	columnPtr = editPtr->columnPtr;
	objPtr = Tcl_NewStringObj(editPtr->string, -1);
	if (Blt_TreeSetValueByUid(htabPtr->tree, entryPtr->node, 
		  columnPtr->keyUid, objPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	entryPtr->flags |= ENTRY_DIRTY;
    }	
    Blt_HtConfigureEntry(htabPtr, entryPtr);
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    Tk_UnmapWindow(editPtr->tkwin);
    return TCL_OK;
}

/*ARGSUSED*/
static int
CancelOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Tk_UnmapWindow(editPtr->tkwin);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    return Tk_ConfigureValue(interp, editPtr->tkwin, editorConfigSpecs, 
	(char *)editPtr, argv[3], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 * 	This procedure is called to process a list of configuration
 *	options database, in order to reconfigure the one of more
 *	entries in the widget.
 *
 *	  .h text configure option value
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for htabPtr; old resources get freed, if there
 *	were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 3) {
	return Tk_ConfigureInfo(interp, editPtr->tkwin, editorConfigSpecs, 
		(char *)editPtr, (char *)NULL, 0);
    } else if (argc == 4) {
	return Tk_ConfigureInfo(interp, editPtr->tkwin, editorConfigSpecs, 
		(char *)editPtr, argv[3], 0);
    }
    if (Tk_ConfigureWidget(interp, editPtr->tkwin, editorConfigSpecs, 
	argc - 3, argv + 3, (char *)editPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    ConfigureEditor(editPtr);
    EventuallyRedraw(editPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *	Remove one or more characters from the label of an entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory gets freed, the entry gets modified and (eventually)
 *	redisplayed.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int firstPos, lastPos;

    if (editPtr->entryPtr == NULL) {
	return TCL_OK;
    }
    if (GetTextIndex(editPtr, argv[3], &firstPos) != TCL_OK) {
	return TCL_ERROR;
    }
    lastPos = firstPos;
    if ((argc == 5) && (GetTextIndex(editPtr, argv[4], &lastPos) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (firstPos > lastPos) {
	return TCL_OK;
    }
    return DeleteText(editPtr, firstPos, lastPos);
}

/*ARGSUSED*/
static int
GetOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int x, y;
    int worldX;
    Blt_ChainLink *linkPtr;
    Column *columnPtr;
    Entry *entryPtr;

    if ((Tcl_GetInt(interp, argv[3], &x) != TCL_OK) ||
	(Tcl_GetInt(interp, argv[4], &y) != TCL_OK)) {
	return TCL_ERROR;
    }
    entryPtr = Blt_HtNearestEntry(editPtr->htabPtr, x, y, FALSE);
    if (entryPtr == NULL) {
	return TCL_OK;
    }
    worldX = WORLDX(editPtr->htabPtr, x);
    columnPtr = NULL;		/* Suppress compiler warning. */
    for (linkPtr = Blt_ChainFirstLink(editPtr->htabPtr->chainPtr); 
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	if ((worldX >= columnPtr->worldX) && 
	    (worldX < (columnPtr->worldX + columnPtr->width))) {
	    break;
	}
    }
    if ((linkPtr == NULL) || (!columnPtr->editable)) {
	return TCL_OK;
    }
    AcquireText(editPtr, entryPtr, columnPtr);
    editPtr->insertPos = strlen(editPtr->string);
    Tk_MapWindow(editPtr->tkwin);
    EventuallyRedraw(editPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IcursorOp --
 *
 *	Returns the numeric index of the given string. Indices can be
 *	one of the following:
 *
 *	"anchor"	Selection anchor.
 *	"end"		End of the label.
 *	"insert"	Insertion cursor.
 *	"sel.first"	First character selected.
 *	"sel.last"	Last character selected.
 *	@x,y		Index at X-Y screen coordinate.
 *	number		Returns the same number.
 *
 * Results:
 *	A standard Tcl result.  If the argument does not represent a
 *	valid label index, then TCL_ERROR is returned and the interpreter
 *	result will contain an error message.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IcursorOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int textPos;

    if (GetTextIndex(editPtr, argv[3], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    editPtr->insertPos = textPos;
    IndexToPointer(editPtr);
    EventuallyRedraw(editPtr);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * IndexOp --
 *
 *	Returns the numeric index of the given string. Indices can be
 *	one of the following:
 *
 *	"anchor"	Selection anchor.
 *	"end"		End of the label.
 *	"insert"	Insertion cursor.
 *	"sel.first"	First character selected.
 *	"sel.last"	Last character selected.
 *	@x,y		Index at X-Y screen coordinate.
 *	number		Returns the same number.
 *
 * Results:
 *	A standard Tcl result.  If the argument does not represent a
 *	valid label index, then TCL_ERROR is returned and the interpreter
 *	result will contain an error message.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IndexOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int textPos;

    if (GetTextIndex(editPtr, argv[3], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    if (editPtr->string != NULL) {
	int nChars;

	nChars = Tcl_NumUtfChars(editPtr->string, textPos);
	Tcl_SetResult(interp, Blt_Itoa(nChars), TCL_VOLATILE);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * InsertOp --
 *
 *	Add new characters to the label of an entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	New information gets added to editPtr;  it will be redisplayed
 *	soon, but not necessarily immediately.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InsertOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int extra;
    int insertPos;

    if (editPtr->entryPtr == NULL) {
	return TCL_ERROR;
    }
    if (GetTextIndex(editPtr, argv[3], &insertPos) != TCL_OK) {
	return TCL_ERROR;
    }
    extra = strlen(argv[4]);
    if (extra == 0) {	/* Nothing to insert. Move the cursor anyways. */
	editPtr->insertPos = insertPos;
    } else {
	InsertText(editPtr, argv[4], insertPos, extra);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionAdjustOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int textPos;
    int half1, half2;

    if (GetTextIndex(editPtr, argv[4], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    half1 = (editPtr->selFirst + editPtr->selLast) / 2;
    half2 = (editPtr->selFirst + editPtr->selLast + 1) / 2;
    if (textPos < half1) {
	editPtr->selAnchor = editPtr->selLast;
    } else if (textPos > half2) {
	editPtr->selAnchor = editPtr->selFirst;
    }
    return SelectText(editPtr, textPos);
}

/*ARGSUSED*/
static int
SelectionClearOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    if (editPtr->selFirst != -1) {
	editPtr->selFirst = editPtr->selLast = -1;
	EventuallyRedraw(editPtr);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionFromOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int textPos;

    if (GetTextIndex(editPtr, argv[4], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    editPtr->selAnchor = textPos;
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionPresentOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Tcl_AppendResult(interp, (editPtr->selFirst == -1) ? "0" : "1", 
		(char *)NULL);
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionRangeOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int selFirst, selLast;

    if (GetTextIndex(editPtr, argv[4], &selFirst) != TCL_OK) {
	return TCL_ERROR;
    }
    if (GetTextIndex(editPtr, argv[5], &selLast) != TCL_OK) {
	return TCL_ERROR;
    }
    editPtr->selAnchor = selFirst;
    return SelectText(editPtr, selLast);
}

/*ARGSUSED*/
static int
SelectionToOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int textPos;

    if (GetTextIndex(editPtr, argv[4], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    return SelectText(editPtr, textPos);
}


static Blt_OpSpec textSelectionOps[] =
{
    {"adjust", 1, (Blt_OpProc)SelectionAdjustOp, 5, 5, "index",},
    {"clear", 1, (Blt_OpProc)SelectionClearOp, 4, 4, "",},
    {"from", 1, (Blt_OpProc)SelectionFromOp, 5, 5, "index"},
    {"present", 1, (Blt_OpProc)SelectionPresentOp, 4, 4, ""},
    {"range", 1, (Blt_OpProc)SelectionRangeOp, 6, 6, "start end",},
    {"to", 1, (Blt_OpProc)SelectionToOp, 5, 5, "index"},
};

static int nTextSelectionOps = sizeof(textSelectionOps) / sizeof(Blt_OpSpec);

/*
 *	This procedure handles the individual options for text
 *	selections.  The selected text is designated by start and end
 *	indices into the text pool.  The selected segment has both a
 *	anchored and unanchored ends.  The following selection
 *	operations are implemented:
 *
 *	  "adjust"	- resets either the first or last index
 *			  of the selection.
 *	  "clear"	- clears the selection. Sets first/last
 *			  indices to -1.
 *	  "from"	- sets the index of the selection anchor.
 *	  "present"	- return "1" if a selection is available,
 *			  "0" otherwise.
 *	  "range"	- sets the first and last indices.
 *	  "to"		- sets the index of the un-anchored end.
 */
static int
SelectionOp(editPtr, interp, argc, argv)
    Editor *editPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nTextSelectionOps, textSelectionOps, 
	BLT_OPER_ARG3, argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (editPtr, interp, argc, argv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtTextOp --
 *
 *	This procedure handles entry operations.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

static Blt_OpSpec textOps[] =
{
    {"apply", 1, (Blt_OpProc)ApplyOp, 3, 3, "",},
    {"cancel", 2, (Blt_OpProc)CancelOp, 3, 3, "",},
    {"cget", 2, (Blt_OpProc)CgetOp, 4, 4, "value",},
    {"configure", 2, (Blt_OpProc)ConfigureOp, 3, 0, "?option value...?",},
    {"delete", 1, (Blt_OpProc)DeleteOp, 4, 5, "first last"},
    {"get", 1, (Blt_OpProc)GetOp, 5, 5, "get x y",},
    {"icursor", 2, (Blt_OpProc)IcursorOp, 4, 4, "index"},
    {"index", 3, (Blt_OpProc)IndexOp, 4, 4, "index"},
    {"insert", 3, (Blt_OpProc)InsertOp, 5, 5, "index string"},
    {"selection", 3, (Blt_OpProc)SelectionOp, 3, 0, "args"},
};
static int nTextOps = sizeof(textOps) / sizeof(Blt_OpSpec);

int
Blt_HtTextOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nTextOps, textOps, BLT_OPER_ARG2, 
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (htabPtr->editPtr, interp, argc, argv);
    return result;
}

#endif
