/*
  Remember row,column where string was acquired.
  postcombobox x y 
	icon?, text, row, column position, fg, bg button?
	based upon style.
  grab set
  SetIcon
  SetText
  SetBg
  SetFg
  SetFont
  SetButton  
 */

/*
 * bltTreeViewEdit.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 "treeview" widget was created by George A. Howlett.
 */

#include "bltInt.h"

#ifndef NO_TREEVIEW

#include "bltTreeView.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#define TEXTBOX_FOCUS	(1<<0)
#define TEXTBOX_REDRAW	(1<<1)

static Tcl_IdleProc DisplayTextbox;
static Tcl_FreeProc DestroyTextbox;
static Tcl_TimerProc BlinkCursorProc;
static Tcl_ObjCmdProc TextboxCmd;

/*
 * Textbox --
 *
 *	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.
 */
typedef struct {

    /*
     * This is a SNAFU in the Tk API.  It assumes that only an official
     * Tk "toplevel" widget will ever become a toplevel window (i.e. a
     * window whose parent is the root window).  Because under Win32,
     * Tk tries to use the widget record associated with the TopLevel
     * as a Tk frame widget, to read its menu name.  What this means
     * is that any widget that's going to be a toplevel, must also look
     * like a frame. Therefore we've copied the frame widget structure
     * fields into the token.
     */

    Tk_Window tkwin;		/* Window that embodies the frame.  NULL
				 * means that the window has been destroyed
				 * but the data structures haven't yet been
				 * cleaned up. */
    Display *display;		/* Display containing widget.  Used, among
				 * other things, so that resources can be
				 * freed even after tkwin has gone away. */
    Tcl_Interp *interp;		/* Interpreter associated with widget.  Used
				 * to delete widget command. */
    Tcl_Command widgetCmd;	/* Token for frame's widget command. */
    char *className;		/* Class name for widget (from configuration
				 * option).  Malloc-ed. */
    int mask;			/* Either FRAME or TOPLEVEL;  used to select
				 * which configuration options are valid for
				 * widget. */
    char *screenName;		/* Screen on which widget is created.  Non-null
				 * only for top-levels.  Malloc-ed, may be
				 * NULL. */
    char *visualName;		/* Textual description of visual for window,
				 * from -visual option.  Malloc-ed, may be
				 * NULL. */
    char *colormapName;		/* Textual description of colormap for window,
				 * from -colormap option.  Malloc-ed, may be
				 * NULL. */
    char *menuName;		/* Textual description of menu to use for
				 * menubar. Malloc-ed, may be NULL. */
    Colormap colormap;		/* If not None, identifies a colormap
				 * allocated for this window, which must be
				 * freed when the window is deleted. */
    Tk_3DBorder border;		/* Structure used to draw 3-D border and
				 * background.  NULL means no background
				 * or border. */
    int borderWidth;		/* Width of 3-D border (if any). */
    int relief;			/* 3-d effect: TK_RELIEF_RAISED etc. */
    int highlightWidth;		/* Width in pixels of highlight to draw
				 * around widget when it has the focus.
				 * 0 means don't draw a highlight. */
    XColor *highlightBgColorPtr;
				/* Color for drawing traversal highlight
				 * area when highlight is off. */
    XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
    int width;			/* Width to request for window.  <= 0 means
				 * don't request any size. */
    int height;			/* Height to request for window.  <= 0 means
				 * don't request any size. */
    Tk_Cursor cursor;		/* Current cursor for window, or None. */
    char *takeFocus;		/* Value of -takefocus option;  not used in
				 * the C code, but used by keyboard traversal
				 * scripts.  Malloc'ed, but may be NULL. */
    int isContainer;		/* 1 means this window is a container, 0 means
				 * that it isn't. */
    char *useThis;		/* If the window is embedded, this points to
				 * the name of the window in which it is
				 * embedded (malloc'ed).  For non-embedded
				 * windows this is NULL. */
    int flags;			/* Various flags;  see below for
				 * definitions. */

    /* Textbox-specific fields */
    TreeView *tvPtr;
    int x, y;			/* Position of 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. */

    int cursorX, cursorY;	/* Position of the insertion cursor in the
				 * textbox 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. */
    TreeViewEntry *entryPtr;	/* Selected entry */
    TreeViewColumn *columnPtr;	/* Column of entry to be edited */
    TreeViewStyle *stylePtr;
    TreeViewIcon icon;
    int gap;
    char *string;
    TextLayout *textPtr;
    Tk_Font font;
    GC gc;

    Tk_3DBorder selBorder;
    int selRelief;
    int selBorderWidth;
    XColor *selFgColor;		/* Text color of a selected entry. */
    int buttonBorderWidth;
    Tk_3DBorder buttonBorder;
    int buttonRelief;
} Textbox;

#define DEF_TEXTBOX_BACKGROUND		RGB_WHITE
#define DEF_TEXTBOX_BORDERWIDTH	STD_BORDERWIDTH
#ifdef WIN32
#define DEF_TEXTBOX_BUTTON_BACKGROUND  RGB_GREY85
#else
#define DEF_TEXTBOX_BUTTON_BACKGROUND  RGB_GREY90
#endif
#define DEF_TEXTBOX_BUTTON_BORDERWIDTH	"2"
#define DEF_TEXTBOX_BUTTON_RELIEF	"raised"
#define DEF_TEXTBOX_CURSOR		(char *)NULL
#define DEF_TEXTBOX_EXPORT_SELECTION	"no"
#define DEF_TEXTBOX_NORMAL_BACKGROUND 	STD_NORMAL_BACKGROUND
#define DEF_TEXTBOX_NORMAL_FG_MONO	STD_ACTIVE_FG_MONO
#define DEF_TEXTBOX_RELIEF		"sunken"
#define DEF_TEXTBOX_SELECT_BACKGROUND 	RGB_LIGHTBLUE0
#define DEF_TEXTBOX_SELECT_BG_MONO  	STD_SELECT_BG_MONO
#define DEF_TEXTBOX_SELECT_BORDERWIDTH "1"
#define DEF_TEXTBOX_SELECT_FOREGROUND 	STD_SELECT_FOREGROUND
#define DEF_TEXTBOX_SELECT_FG_MONO  	STD_SELECT_FG_MONO
#define DEF_TEXTBOX_SELECT_RELIEF	"flat"

/* Textbox Procedures */
static Blt_ConfigSpec textboxConfigSpecs[] =
{
    {BLT_CONFIG_BORDER, "-background", "background", "Background",
	DEF_TEXTBOX_BACKGROUND, Blt_Offset(Textbox, border), 0},
    {BLT_CONFIG_SYNONYM, "-bd",  (char *)NULL, (char *)NULL, (char *)NULL, 0,0, (ClientData)"-borderwidth"},
    {BLT_CONFIG_SYNONYM, "-bg",  (char *)NULL, (char *)NULL, (char *)NULL, 0,0, (ClientData)"-background"},
    {BLT_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_TEXTBOX_CURSOR, Blt_Offset(Textbox, cursor), 
	BLT_CONFIG_NULL_OK},
    {BLT_CONFIG_DISTANCE, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_TEXTBOX_BORDERWIDTH, Blt_Offset(Textbox, borderWidth),
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_BORDER, "-buttonbackground", "buttonBackground", 
	"ButtonBackground", DEF_TEXTBOX_BUTTON_BACKGROUND,
	Blt_Offset(Textbox, buttonBorder), 0},
    {BLT_CONFIG_RELIEF, "-buttonrelief", "buttonRelief", "ButtonRelief",
	DEF_TEXTBOX_BUTTON_RELIEF, Blt_Offset(Textbox, buttonRelief),
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_DISTANCE, "-buttonborderwidth", "buttonBorderWidth", 
	"ButtonBorderWidth", DEF_TEXTBOX_BUTTON_BORDERWIDTH, 
	Blt_Offset(Textbox, buttonBorderWidth),
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
	"ExportSelection", DEF_TEXTBOX_EXPORT_SELECTION, 
	Blt_Offset(Textbox, exportSelection), 
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_TEXTBOX_RELIEF, Blt_Offset(Textbox, relief), 0},
    {BLT_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
	DEF_TEXTBOX_SELECT_BG_MONO, Blt_Offset(Textbox, selBorder),
	BLT_CONFIG_MONO_ONLY},
    {BLT_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
	DEF_TEXTBOX_SELECT_BACKGROUND, Blt_Offset(Textbox, selBorder),
	BLT_CONFIG_COLOR_ONLY},
    {BLT_CONFIG_DISTANCE, "-selectborderwidth", "selectBorderWidth", 
        "BorderWidth", DEF_TEXTBOX_SELECT_BORDERWIDTH, 
	Blt_Offset(Textbox, selBorderWidth), 
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",

	DEF_TEXTBOX_SELECT_FG_MONO, Blt_Offset(Textbox, selFgColor),
	BLT_CONFIG_MONO_ONLY},
    {BLT_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
	DEF_TEXTBOX_SELECT_FOREGROUND, Blt_Offset(Textbox, selFgColor),
	BLT_CONFIG_COLOR_ONLY},
    {BLT_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief",
	DEF_TEXTBOX_SELECT_RELIEF, Blt_Offset(Textbox, selRelief),
	BLT_CONFIG_DONT_SET_DEFAULT},
    {BLT_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 
	0, 0}
};

static Tk_LostSelProc TextboxLostSelectionProc;
static Tk_SelectionProc TextboxSelectionProc;
static Tk_EventProc TextboxEventProc;

/*
 *----------------------------------------------------------------------
 *
 * 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(tbPtr)
    Textbox *tbPtr;
{
    if ((tbPtr->tkwin != NULL) && 
	((tbPtr->flags & TEXTBOX_REDRAW) == 0)) {
	tbPtr->flags |= TEXTBOX_REDRAW;
	Tcl_DoWhenIdle(DisplayTextbox, tbPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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. */
{
    Textbox *tbPtr = clientData;
    int interval;

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

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

    if (eventPtr->type == Expose) {
	if (eventPtr->xexpose.count == 0) {
	    EventuallyRedraw(tbPtr);
	}
    } else if (eventPtr->type == ConfigureNotify) {
	EventuallyRedraw(tbPtr);
    } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
	if (eventPtr->xfocus.detail == NotifyInferior) {
	    return;
	}
	if (eventPtr->type == FocusIn) {
	    tbPtr->flags |= TEXTBOX_FOCUS;
	} else {
	    tbPtr->flags &= ~TEXTBOX_FOCUS;
	}
	Tcl_DeleteTimerHandler(tbPtr->timerToken);
	if ((tbPtr->active) && (tbPtr->flags & TEXTBOX_FOCUS)) {
	    tbPtr->cursorOn = TRUE;
	    if (tbPtr->offTime != 0) {
		tbPtr->timerToken = Tcl_CreateTimerHandler(tbPtr->onTime, 
		   BlinkCursorProc, tbPtr);
	    }
	} else {
	    tbPtr->cursorOn = FALSE;
	    tbPtr->timerToken = (Tcl_TimerToken) NULL;
	}
	EventuallyRedraw(tbPtr);
    } else if (eventPtr->type == DestroyNotify) {
	if (tbPtr->tkwin != NULL) {
	    tbPtr->tkwin = NULL;
	}
	if (tbPtr->flags & TEXTBOX_REDRAW) {
	    Tcl_CancelIdleCall(DisplayTextbox, tbPtr);
	}
	if (tbPtr->timerToken != NULL) {
	    Tcl_DeleteTimerHandler(tbPtr->timerToken);
	}
	tbPtr->tvPtr->comboWin = NULL;
	Tcl_EventuallyFree(tbPtr, DestroyTextbox);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TextboxLostSelectionProc --
 *
 *	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
TextboxLostSelectionProc(clientData)
    ClientData clientData;	/* Information about Text widget. */
{
    Textbox *tbPtr = clientData;

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

static int
PointerToIndex(tbPtr, x, y)
    Textbox *tbPtr;
    int x, y;
{
    TextLayout *textPtr;
    Tk_FontMetrics fontMetrics;
    TextFragment *fragPtr;
    int nBytes;
    register int i;
    int total;

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

    textPtr = tbPtr->textPtr;

    /* Bound the y-coordinate within the window. */
    if (y < 0) {
	y = 0;
    } else if (y >= textPtr->height) {
	y = textPtr->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(tbPtr->font, &fontMetrics);
    fragPtr = textPtr->fragArr;
    total = 0;
    for (i = (y / fontMetrics.linespace); i > 0; i--) {
	total += fragPtr->count;
	fragPtr++;
    }
    if (x < 0) {
	nBytes = 0;
    } else if (x >= textPtr->width) {
	nBytes = fragPtr->count;
    } else {
	int newX;

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

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

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

static int
IndexToPointer(tbPtr)
    Textbox *tbPtr;
{
    int x, y;
    int maxLines;
    TextLayout *textPtr;
    Tk_FontMetrics fontMetrics;
    int nBytes;
    int sum;
    TextFragment *fragPtr;
    register int i;

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

    sum = 0;
    x = y = tbPtr->borderWidth;
    if (tbPtr->icon != NULL) {
	x += TreeViewIconWidth(tbPtr->icon) + 2 * tbPtr->gap;
    }
    fragPtr = textPtr->fragArr;
    for (i = 0; i <= maxLines; i++) {
	/* Total the number of bytes on each line.  Include newlines. */
	nBytes = fragPtr->count + 1;
	if ((sum + nBytes) > tbPtr->insertPos) {
	    x += Tk_TextWidth(tbPtr->font, fragPtr->text, 
		tbPtr->insertPos - sum);
	    break;
	}
	y += fontMetrics.linespace;
	sum += nBytes;
	fragPtr++;
    }
    tbPtr->cursorX = x;
    tbPtr->cursorY = y;
    tbPtr->cursorHeight = fontMetrics.linespace;
    tbPtr->cursorWidth = 3;
    return TCL_OK;
}

static void
UpdateLayout(tbPtr)
    Textbox *tbPtr;
{
    TextStyle ts;
    int width, height;
    TextLayout *textPtr;
    int gap;
    int iconWidth, iconHeight;

    gap = iconWidth = iconHeight = 0;
    if (tbPtr->icon != NULL) {
	iconWidth = TreeViewIconWidth(tbPtr->icon) + 4;
	iconHeight = TreeViewIconHeight(tbPtr->icon);
	gap = tbPtr->gap;
    }

    /* The layout is based upon the current font. */
    Blt_InitTextStyle(&ts);
    ts.anchor = TK_ANCHOR_NW;
    ts.justify = TK_JUSTIFY_LEFT;
    ts.font = tbPtr->font;
    textPtr = Blt_GetTextLayout(tbPtr->string, &ts);
    if (tbPtr->textPtr != NULL) {
	Blt_Free(tbPtr->textPtr);
    }
    tbPtr->textPtr = textPtr;

    width = iconWidth + textPtr->width + gap * 2;
    height = MAX(iconHeight, textPtr->height);

    if (width <= tbPtr->columnPtr->width) {
	width = tbPtr->columnPtr->width;
    }
    if (height < tbPtr->entryPtr->height) {
	height = tbPtr->entryPtr->height;
    }
    tbPtr->width = width + (2 * tbPtr->borderWidth);
    tbPtr->height = height + (2 * tbPtr->borderWidth);
    IndexToPointer(tbPtr);
    Tk_MoveResizeWindow(tbPtr->tkwin, tbPtr->x, tbPtr->y, 
	      tbPtr->width, tbPtr->height);
    Tk_MapWindow(tbPtr->tkwin);
    XRaiseWindow(tbPtr->display, Tk_WindowId(tbPtr->tkwin));
}

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

    oldText = tbPtr->string;
    oldSize = strlen(oldText);
    newSize = oldSize + nBytes;
    newText = Blt_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 (tbPtr->selFirst >= insertPos) {
	tbPtr->selFirst += nBytes;
    }
    if (tbPtr->selLast > insertPos) {
	tbPtr->selLast += nBytes;
    }
    if ((tbPtr->selAnchor > insertPos) || (tbPtr->selFirst >= insertPos)) {
	tbPtr->selAnchor += nBytes;
    }
    if (tbPtr->string != NULL) {
	Blt_Free(tbPtr->string);
    }
    tbPtr->string = newText;
    tbPtr->insertPos = insertPos + nBytes;
    UpdateLayout(tbPtr);
}

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

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

    nBytes = lastPos - firstPos;

    oldSize = strlen(oldText) + 1;
    newSize = oldSize - nBytes + 1;
    newText = Blt_Malloc(sizeof(char) * newSize);
    p = newText;
    if (firstPos > 0) {
	strncpy(p, oldText, firstPos);
	p += firstPos;
    }
    *p = '\0';
    if (lastPos < oldSize) {
	strcpy(p, oldText + lastPos);
    }
    Blt_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 (tbPtr->selFirst >= firstPos) {
	if (tbPtr->selFirst >= lastPos) {
	    tbPtr->selFirst -= nBytes;
	} else {
	    tbPtr->selFirst = firstPos;
	}
    }
    if (tbPtr->selLast >= firstPos) {
	if (tbPtr->selLast >= lastPos) {
	    tbPtr->selLast -= nBytes;
	} else {
	    tbPtr->selLast = firstPos;
	}
    }
    if (tbPtr->selLast <= tbPtr->selFirst) {
	tbPtr->selFirst = tbPtr->selLast = -1; /* Cut away the entire
						    * selection. */ 
    }
    if (tbPtr->selAnchor >= firstPos) {
	if (tbPtr->selAnchor >= lastPos) {
	    tbPtr->selAnchor -= nBytes;
	} else {
	    tbPtr->selAnchor = firstPos;
	}
    }
    if (tbPtr->insertPos >= firstPos) {
	if (tbPtr->insertPos >= lastPos) {
	    tbPtr->insertPos -= nBytes;
	} else {
	    tbPtr->insertPos = firstPos;
	}
    }
    tbPtr->string = newText;
    UpdateLayout(tbPtr);
    EventuallyRedraw(tbPtr);
    return TCL_OK;
}

static int
AcquireText(tvPtr, tbPtr, entryPtr, columnPtr)
    TreeView *tvPtr;
    Textbox *tbPtr;
    TreeViewEntry *entryPtr;
    TreeViewColumn *columnPtr;
{
    TreeViewStyle *stylePtr;
    int x, y;
    char *string;
    TreeViewIcon icon;

    if (columnPtr == &tvPtr->treeColumn) {
	int level;

	level = DEPTH(tvPtr, entryPtr->node);
	x = SCREENX(tvPtr, entryPtr->worldX);
	y = SCREENY(tvPtr, entryPtr->worldY);
	x += ICONWIDTH(level) + ICONWIDTH(level + 1) + 4;
	string = GETLABEL(entryPtr);
	stylePtr = columnPtr->stylePtr;
	icon = Blt_TreeViewGetEntryIcon(tvPtr, entryPtr);
    } else {
	TreeViewValue *valuePtr;

	x = SCREENX(tvPtr, columnPtr->worldX);
	y = SCREENY(tvPtr, entryPtr->worldY);
	stylePtr = columnPtr->stylePtr;
	valuePtr = Blt_TreeViewFindValue(entryPtr, columnPtr);
	string = valuePtr->string;
	if (valuePtr->stylePtr != NULL) {
	    stylePtr = valuePtr->stylePtr;
	}
	icon = stylePtr->icon;
    }
    if (tbPtr->textPtr != NULL) {
	Blt_Free(tbPtr->textPtr);
	tbPtr->textPtr = NULL;
    }
    if (tbPtr->string != NULL) {
	Blt_Free(tbPtr->string);
    }
    if (string == NULL) {
	string = "";
    }
    tbPtr->icon = icon;
    tbPtr->entryPtr = entryPtr;
    tbPtr->columnPtr = columnPtr;
    tbPtr->x = x - tbPtr->borderWidth;
    tbPtr->y = y - tbPtr->borderWidth;
    
    tbPtr->gap = stylePtr->gap;
    tbPtr->string = Blt_Strdup(string);
    tbPtr->gc = Blt_TreeViewGetStyleGC(tvPtr, stylePtr);
    tbPtr->font = Blt_TreeViewGetStyleFont(tvPtr, columnPtr, stylePtr);
    tbPtr->selFirst = tbPtr->selLast = -1;
    UpdateLayout(tbPtr);
    Tk_MapWindow(tbPtr->tkwin);
    EventuallyRedraw(tbPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * GetIndexFromObj --
 *
 *	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
GetIndexFromObj(interp, tbPtr, objPtr, indexPtr)
    Tcl_Interp *interp;
    Textbox *tbPtr;
    Tcl_Obj *objPtr;
    int *indexPtr;
{
    int textPos;
    char c;
    char *string;

    string = Tcl_GetString(objPtr);
    if ((tbPtr->string == NULL) || (tbPtr->string[0] == '\0')) {
	*indexPtr = 0;
	return TCL_OK;
    }
    c = string[0];
    if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
	textPos = tbPtr->selAnchor;
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
	textPos = strlen(tbPtr->string);
    } else if ((c == 'i') && (strcmp(string, "insert") == 0)) {
	textPos = tbPtr->insertPos;
    } else if ((c == 'n') && (strcmp(string, "next") == 0)) {
	textPos = tbPtr->insertPos;
	if (textPos < (int)strlen(tbPtr->string)) {
	    textPos++;
	}
    } else if ((c == 'l') && (strcmp(string, "last") == 0)) {
	textPos = tbPtr->insertPos;
	if (textPos > 0) {
	    textPos--;
	}
    } else if ((c == 's') && (strcmp(string, "sel.first") == 0)) {
	if (tbPtr->selFirst < 0) {
	    textPos = -1;
	} else {
	    textPos = tbPtr->selFirst;
	}
    } else if ((c == 's') && (strcmp(string, "sel.last") == 0)) {
	if (tbPtr->selLast < 0) {
	    textPos = -1;
	} else {
	    textPos = tbPtr->selLast;
	}
    } else if (c == '@') {
	int x, y;

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

	if (Tcl_GetIntFromObj(interp, objPtr, &number) != TCL_OK) {
	    return TCL_ERROR;
	}
	/* Don't allow the index to point outside the label. */
	maxChars = Tcl_NumUtfChars(tbPtr->string, -1);
	if (number < 0) {
	    textPos = 0;
	} else if (number > maxChars) {
	    textPos = strlen(tbPtr->string);
	} else {
	    textPos = Tcl_UtfAtIndex(tbPtr->string, number) - 
		tbPtr->string;
	}
    } else {
	if (interp != NULL) {
	    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(tbPtr, textPos)
    Textbox *tbPtr; 	/* Information about textbox. */
    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 ((tbPtr->exportSelection) && (tbPtr->selFirst == -1)) {
	Tk_OwnSelection(tbPtr->tkwin, XA_PRIMARY, 
			TextboxLostSelectionProc, tbPtr);
    }
    /*  If the anchor hasn't been set yet, assume the beginning of the text*/
    if (tbPtr->selAnchor < 0) {
	tbPtr->selAnchor = 0;
    }
    if (tbPtr->selAnchor <= textPos) {
	selFirst = tbPtr->selAnchor;
	selLast = textPos;
    } else {
	selFirst = textPos;
	selLast = tbPtr->selAnchor;
    }
    if ((tbPtr->selFirst != selFirst) || (tbPtr->selLast != selLast)) {
	tbPtr->selFirst = selFirst;
	tbPtr->selLast = selLast;
	EventuallyRedraw(tbPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TextboxSelectionProc --
 *
 *	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
TextboxSelectionProc(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. */
{
    Textbox *tbPtr = clientData;
    int size;

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


static void
DestroyTextbox(data)
    DestroyData data;
{
    Textbox *tbPtr = (Textbox *)data;

    Blt_FreeObjOptions(tbPtr->interp, textboxConfigSpecs, (char *)tbPtr, 
	tbPtr->display, 0);

    if (tbPtr->string != NULL) {
	Blt_Free(tbPtr->string);
    }
    if (tbPtr->textPtr != NULL) {
	Blt_Free(tbPtr->textPtr);
    }
    if (tbPtr->timerToken != NULL) {
	Tcl_DeleteTimerHandler(tbPtr->timerToken);
    }
    if (tbPtr->tkwin != NULL) {
	Tk_DeleteSelHandler(tbPtr->tkwin, XA_PRIMARY, XA_STRING);
    }
    Blt_Free(tbPtr);
}

static void
ConfigureTextbox(tbPtr)
    Textbox *tbPtr;
{
#ifdef notdef
    GC newGC;
    Textbox *tbPtr = tvPtr->tbPtr;
    XGCValues gcValues;
    unsigned long gcMask;

    /*
     * GC for edit window.
     */
    gcMask = 0;
    newGC = Tk_GetGC(tbPtr->tkwin, gcMask, &gcValues);
    if (tbPtr->gc != NULL) {
	Tk_FreeGC(tbPtr->display, tbPtr->gc);
    }
    tbPtr->gc = newGC;
    tbPtr->width = tbPtr->textPtr->width + 
	2 * (tbPtr->borderWidth + tbPtr->highlightWidth);
    tbPtr->height = tbPtr->textPtr->height + 
	2 * (tbPtr->borderWidth + tbPtr->highlightWidth);
    
    if (Tk_IsMapped(tbPtr->tkwin)) {
	if ((tbPtr->height != Tk_Height(tbPtr->tkwin)) ||
	    (tbPtr->width != Tk_Width(tbPtr->tkwin))) {
	    Tk_ResizeWindow(tbPtr->tkwin, tbPtr->width, tbPtr->height);
	}
    }
#endif
}

int
Blt_TreeViewTextbox(tvPtr, entryPtr, columnPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
    TreeViewColumn *columnPtr;
{
    Tk_Window tkwin;
    Textbox *tbPtr;
    char editClass[20];

    if (tvPtr->comboWin != NULL) {
	Tk_DestroyWindow(tvPtr->comboWin);
    }
    tkwin = Tk_CreateWindow(tvPtr->interp, tvPtr->tkwin, "edit", (char *)NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }

    Tk_MakeWindowExist(tkwin);

    sprintf(editClass, "%sEditor", Tk_Class(tvPtr->tkwin));
    Tk_SetClass(tkwin, editClass); 

    tbPtr = Blt_Calloc(1, sizeof(Textbox));
    assert(tbPtr);

    tbPtr->interp = tvPtr->interp;
    tbPtr->display = Tk_Display(tkwin);
    tbPtr->tkwin = tkwin;
    tbPtr->borderWidth = 1;
    tbPtr->relief = TK_RELIEF_SOLID;
    tbPtr->selRelief = TK_RELIEF_FLAT;
    tbPtr->selBorderWidth = 1;
    tbPtr->selAnchor = -1;
    tbPtr->selFirst = tbPtr->selLast = -1;
    tbPtr->onTime = 600;
    tbPtr->active = TRUE;
    tbPtr->offTime = 300;
    tbPtr->tvPtr = tvPtr;
    tbPtr->buttonRelief = TK_RELIEF_SUNKEN;
    tbPtr->buttonBorderWidth = 1;
    tvPtr->comboWin = tkwin;
#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, tbPtr);
#endif
    Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, 
	TextboxSelectionProc, tbPtr, XA_STRING);
    Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask |
	FocusChangeMask, TextboxEventProc, tbPtr);
    Tcl_CreateObjCommand(tvPtr->interp, Tk_PathName(tkwin), 
	TextboxCmd, tbPtr, NULL);
    if (Blt_ConfigureWidgetFromObj(tvPtr->interp, tkwin, textboxConfigSpecs, 0, 
	(Tcl_Obj **)NULL, (char *)tbPtr, 0, NULL) != TCL_OK) {
	Tk_DestroyWindow(tkwin);
	return TCL_ERROR;
    }
    AcquireText(tvPtr, tbPtr, entryPtr, columnPtr);
    tbPtr->insertPos = strlen(tbPtr->string);
    
    Tk_MoveResizeWindow(tkwin, tbPtr->x, tbPtr->y, tbPtr->width, 
	tbPtr->height);
    Tk_MapWindow(tkwin);
    Tk_MakeWindowExist(tkwin);
    XRaiseWindow(tbPtr->display, Tk_WindowId(tkwin));
    EventuallyRedraw(tbPtr);
    return TCL_OK;
}

static void
DisplayTextbox(clientData)
    ClientData clientData;
{
    Textbox *tbPtr = clientData;
    Pixmap drawable;
    register int i;
    int x1, x2;
    int count, nChars;
    int leftPos, rightPos;
    int selStart, selEnd, selLength;
    int x, y;
    TextFragment *fragPtr;
    Tk_FontMetrics fontMetrics;
#ifdef notdef
    int buttonX, buttonY, buttonWidth, buttonHeight;
#endif
    tbPtr->flags &= ~TEXTBOX_REDRAW;
    if (!Tk_IsMapped(tbPtr->tkwin)) {
	return;
    }
    if (tbPtr->columnPtr == NULL) {
	return;
    }
    drawable = Tk_GetPixmap(tbPtr->display, Tk_WindowId(tbPtr->tkwin), 
	Tk_Width(tbPtr->tkwin), Tk_Height(tbPtr->tkwin), 
	Tk_Depth(tbPtr->tkwin));

    Blt_Fill3DRectangle(tbPtr->tkwin, drawable, tbPtr->border, 0, 0,
	Tk_Width(tbPtr->tkwin), Tk_Height(tbPtr->tkwin), 
	tbPtr->borderWidth, tbPtr->relief);

    x = tbPtr->borderWidth + tbPtr->gap;
    y = tbPtr->borderWidth;

#ifdef notdef
    buttonX = Tk_Width(tbPtr->tkwin) - 
	(tbPtr->borderWidth + tbPtr->gap + 1);
    buttonY = tbPtr->borderWidth + 1;
#endif

    if (tbPtr->icon != NULL) {
	y += (tbPtr->height - TreeViewIconHeight(tbPtr->icon)) / 2;
	Tk_RedrawImage(TreeViewIconBits(tbPtr->icon), 0, 0, 
		       TreeViewIconWidth(tbPtr->icon), 
		       TreeViewIconHeight(tbPtr->icon), 
		       drawable, x, y);
	x += TreeViewIconWidth(tbPtr->icon) + tbPtr->gap;
    }
    
    Tk_GetFontMetrics(tbPtr->font, &fontMetrics);
    fragPtr = tbPtr->textPtr->fragArr;
    count = 0;
    y = tbPtr->borderWidth;
    if (tbPtr->height > fontMetrics.linespace) {
	y += (tbPtr->height - fontMetrics.linespace) / 2;
    }
    for (i = 0; i < tbPtr->textPtr->nFrags; i++, fragPtr++) {
	leftPos = count;
	count += fragPtr->count;
	rightPos = count;
	if ((rightPos < tbPtr->selFirst) || (leftPos > tbPtr->selLast)) {
	    /* No part of the text fragment is selected. */
	    Tk_DrawChars(tbPtr->display, drawable, tbPtr->gc, 
			 tbPtr->font, fragPtr->text, fragPtr->count, 
			 x + fragPtr->x, y + fragPtr->y);
	    continue;
	}

	/*
	 *  A text fragment can 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 (tbPtr->selFirst > leftPos) {
	    selStart = tbPtr->selFirst;
	}
	if (tbPtr->selLast < rightPos) {
	    selEnd = tbPtr->selLast;
	}
	selLength = (selEnd - selStart);
	x1 = x;

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

	    Tk_MeasureChars(tbPtr->font, fragPtr->text + selStart, selLength,
		10000, DEF_TEXT_FLAGS, &x2);
	    x2 += x;
	    width = (x2 - x1) + 1;
	    Blt_Fill3DRectangle(tbPtr->tkwin, drawable, tbPtr->selBorder,
		x1, y + fragPtr->y - fontMetrics.ascent, 
	        width, fontMetrics.linespace,
		tbPtr->selBorderWidth, tbPtr->selRelief);
	}
	Tk_DrawChars(Tk_Display(tbPtr->tkwin), drawable, tbPtr->gc, 
	     tbPtr->font, fragPtr->text, fragPtr->count, 
	     fragPtr->x + x, fragPtr->y + y);
    }
    if ((tbPtr->flags & TEXTBOX_FOCUS) && (tbPtr->cursorOn)) {
	int left, top, right, bottom;

	IndexToPointer(tbPtr);
	left = tbPtr->cursorX + 1;
	right = left + 1;
	top = tbPtr->cursorY;
	if (tbPtr->height > fontMetrics.linespace) {
	    top += (tbPtr->height - fontMetrics.linespace) / 2;
	}
	bottom = top + tbPtr->cursorHeight - 1;
	XDrawLine(tbPtr->display, drawable, tbPtr->gc, left, top, left,
		bottom);
	XDrawLine(tbPtr->display, drawable, tbPtr->gc, left - 1, top, right,
		top);
	XDrawLine(tbPtr->display, drawable, tbPtr->gc, left - 1, bottom, 
		right, bottom);
    }
#ifdef notdef
    buttonWidth = STD_ARROW_WIDTH + 6 + 2 * tbPtr->buttonBorderWidth;
    buttonX -= buttonWidth;
    buttonHeight = Tk_Height(tbPtr->tkwin) - 4;
    Blt_Fill3DRectangle(tbPtr->tkwin, drawable, tbPtr->buttonBorder, 
	       buttonX, buttonY, buttonWidth, buttonHeight,
	       tbPtr->buttonBorderWidth, tbPtr->buttonRelief); 
    
    buttonX += buttonWidth / 2;
    buttonY = tbPtr->height / 2;
    Blt_DrawArrow(tbPtr->display, drawable, tbPtr->gc, buttonX,
		  buttonY, STD_ARROW_HEIGHT, ARROW_DOWN);
#endif
    Blt_Draw3DRectangle(tbPtr->tkwin, drawable, tbPtr->border, 0, 0,
	Tk_Width(tbPtr->tkwin), Tk_Height(tbPtr->tkwin), 
	tbPtr->borderWidth, tbPtr->relief);
    XCopyArea(tbPtr->display, drawable, Tk_WindowId(tbPtr->tkwin),
	tbPtr->gc, 0, 0, Tk_Width(tbPtr->tkwin), 
	Tk_Height(tbPtr->tkwin), 0, 0);
    Tk_FreePixmap(tbPtr->display, drawable);
}

/*ARGSUSED*/
static int
ApplyOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;	/* Not used. */
{
    TreeViewEntry *entryPtr;
    TreeView *tvPtr = tbPtr->tvPtr;

    entryPtr = tbPtr->entryPtr;
    if (tbPtr->columnPtr == &tvPtr->treeColumn) {
	if (entryPtr->labelUid != NULL) {
	    Blt_TreeViewFreeUid(tvPtr, entryPtr->labelUid);
	}
	if (tbPtr->string == NULL) {
	    entryPtr->labelUid = Blt_TreeViewGetUid(tvPtr, "");
	} else {
	    entryPtr->labelUid = Blt_TreeViewGetUid(tvPtr, tbPtr->string);
	}
    } else {
	TreeViewColumn *columnPtr;
	Tcl_Obj *objPtr;

	columnPtr = tbPtr->columnPtr;
	objPtr = Tcl_NewStringObj(tbPtr->string, -1);
	if (Blt_TreeSetValueByKey(interp, tvPtr->tree, entryPtr->node, 
		columnPtr->key, objPtr) != TCL_OK) {
	    Tcl_DecrRefCount(objPtr);
	    return TCL_ERROR;
	}
	entryPtr->flags |= ENTRY_DIRTY;
    }	
    if (tvPtr != NULL) {
	Blt_TreeViewConfigureEntry(tvPtr, entryPtr, 0, NULL, 
		BLT_CONFIG_OBJV_ONLY);
	tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT);
	Blt_TreeViewEventuallyRedraw(tvPtr);
    }
    Tk_DestroyWindow(tbPtr->tkwin);
    return TCL_OK;
}

/*ARGSUSED*/
static int
CancelOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;	/* Not used. */
{
    Tk_DestroyWindow(tbPtr->tkwin);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;
{
    return Blt_ConfigureValueFromObj(interp, tbPtr->tkwin, 
	textboxConfigSpecs, (char *)tbPtr, objv[2], 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.
 *
 *	  .c 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 tvPtr; old resources get freed, if there
 *	were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    if (objc == 2) {
	return Blt_ConfigureInfoFromObj(interp, tbPtr->tkwin, 
		textboxConfigSpecs, (char *)tbPtr, (Tcl_Obj *)NULL, 0);
    } else if (objc == 3) {
	return Blt_ConfigureInfoFromObj(interp, tbPtr->tkwin, 
		textboxConfigSpecs, (char *)tbPtr, objv[2], 0);
    }
    if (Blt_ConfigureWidgetFromObj(interp, tbPtr->tkwin, 
	textboxConfigSpecs, objc - 2, objv + 2, (char *)tbPtr, 
	BLT_CONFIG_OBJV_ONLY, NULL) != TCL_OK) {
	return TCL_ERROR;
    }
    ConfigureTextbox(tbPtr);
    EventuallyRedraw(tbPtr);
    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(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    int firstPos, lastPos;

    if (tbPtr->entryPtr == NULL) {
	return TCL_OK;
    }
    if (GetIndexFromObj(interp, tbPtr, objv[2], &firstPos) != TCL_OK) {
	return TCL_ERROR;
    }
    lastPos = firstPos;
    if ((objc == 4) && 
	(GetIndexFromObj(interp, tbPtr, objv[3], &lastPos) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (firstPos > lastPos) {
	return TCL_OK;
    }
    return DeleteText(tbPtr, firstPos, lastPos);
}


/*
 *----------------------------------------------------------------------
 *
 * 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(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;
{
    int textPos;

    if (GetIndexFromObj(interp, tbPtr, objv[2], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    if (tbPtr->columnPtr != NULL) {
	tbPtr->insertPos = textPos;
	IndexToPointer(tbPtr);
	EventuallyRedraw(tbPtr);
    }
    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(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;
{
    int textPos;

    if (GetIndexFromObj(interp, tbPtr, objv[2], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((tbPtr->columnPtr != NULL) && (tbPtr->string != NULL)) {
	int nChars;

	nChars = Tcl_NumUtfChars(tbPtr->string, textPos);
	Tcl_SetObjResult(interp, Tcl_NewIntObj(nChars));
    }
    return TCL_OK;
}

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

    if (tbPtr->entryPtr == NULL) {
	return TCL_ERROR;
    }
    if (GetIndexFromObj(interp, tbPtr, objv[2], &insertPos) != TCL_OK) {
	return TCL_ERROR;
    }
    string = Tcl_GetStringFromObj(objv[3], &extra);
    if (extra == 0) {	/* Nothing to insert. Move the cursor anyways. */
	tbPtr->insertPos = insertPos;
    } else {
	InsertText(tbPtr, string, insertPos, extra);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionAdjustOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    int textPos;
    int half1, half2;

    if (GetIndexFromObj(interp, tbPtr, objv[3], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    half1 = (tbPtr->selFirst + tbPtr->selLast) / 2;
    half2 = (tbPtr->selFirst + tbPtr->selLast + 1) / 2;
    if (textPos < half1) {
	tbPtr->selAnchor = tbPtr->selLast;
    } else if (textPos > half2) {
	tbPtr->selAnchor = tbPtr->selFirst;
    }
    return SelectText(tbPtr, textPos);
}

/*ARGSUSED*/
static int
SelectionClearOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;	/* Not used. */
{
    if (tbPtr->selFirst != -1) {
	tbPtr->selFirst = tbPtr->selLast = -1;
	EventuallyRedraw(tbPtr);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionFromOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    int textPos;

    if (GetIndexFromObj(interp, tbPtr, objv[3], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    tbPtr->selAnchor = textPos;
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionPresentOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;	/* Not used. */
{
    int bool;

    bool = (tbPtr->selFirst != -1);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    return TCL_OK;
}

/*ARGSUSED*/
static int
SelectionRangeOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    int selFirst, selLast;

    if (GetIndexFromObj(interp, tbPtr, objv[3], &selFirst) != TCL_OK) {
	return TCL_ERROR;
    }
    if (GetIndexFromObj(interp, tbPtr, objv[4], &selLast) != TCL_OK) {
	return TCL_ERROR;
    }
    tbPtr->selAnchor = selFirst;
    return SelectText(tbPtr, selLast);
}

/*ARGSUSED*/
static int
SelectionToOp(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;		/* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    int textPos;

    if (GetIndexFromObj(interp, tbPtr, objv[3], &textPos) != TCL_OK) {
	return TCL_ERROR;
    }
    return SelectText(tbPtr, textPos);
}


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

static int nSelectionOps = sizeof(selectionOps) / 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(tbPtr, interp, objc, objv)
    Textbox *tbPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nSelectionOps, selectionOps, BLT_OP_ARG2, 
	objc, objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (tbPtr, interp, objc, objv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TextboxCmd --
 *
 *	This procedure handles entry operations.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec comboOps[] =
{
    {"apply", 1, (Blt_Op)ApplyOp, 2, 2, "",},
    {"cancel", 2, (Blt_Op)CancelOp, 2, 2, "",},
    {"cget", 2, (Blt_Op)CgetOp, 3, 3, "value",},
    {"configure", 2, (Blt_Op)ConfigureOp, 2, 0, "?option value...?",},
    {"delete", 1, (Blt_Op)DeleteOp, 3, 4, "first ?last?"},
    {"icursor", 2, (Blt_Op)IcursorOp, 3, 3, "index"},
    {"index", 3, (Blt_Op)IndexOp, 3, 3, "index"},
    {"insert", 3, (Blt_Op)InsertOp, 4, 4, "index string"},
    {"selection", 1, (Blt_Op)SelectionOp, 2, 0, "args"},
};
static int nComboOps = sizeof(comboOps) / sizeof(Blt_OpSpec);

static int
TextboxCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Textbox *tbPtr = clientData;
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nComboOps, comboOps, BLT_OP_ARG1, objc, 
	objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (tbPtr, interp, objc, objv);
    return result;
}

#endif

