/* 
 * tkText.c --
 *
 *	This module provides a big chunk of the implementation of
 *	multi-line editable text widgets for Tk.  Among other things,
 *	it provides the Tcl command interfaces to text widgets and
 *	the display code.  The B-tree representation of text is
 *	implemented elsewhere.
 *
 * Copyright 1992 Regents of the University of California.
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkText.c,v 1.23 92/08/14 14:45:44 ouster Exp $ SPRITE (Berkeley)";
#endif

#include "default.h"
#include "tkConfig.h"
#include "tk.h"
#include "tkText.h"

/*
 * Information used to parse text configuration options:
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
	"ExportSelection", DEF_TEXT_EXPORT_SELECTION,
	Tk_Offset(TkText, exportSelection), 0},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_TEXT_FONT, Tk_Offset(TkText, fontPtr), 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0},
    {TK_CONFIG_INT, "-height", "height", "Height",
	DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0},
    {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
	DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0},
    {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
	DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
	DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
	DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0},
    {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
	DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0},
    {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
	DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0},
    {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
	DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0},
    {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
	DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_ENTRY_SELECT_COLOR, Tk_Offset(TkText, selBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
	DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBorderWidth),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
	DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBorderWidth),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
	DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0},
    {TK_CONFIG_UID, "-state", "state", "State",
	DEF_TEXT_STATE, Tk_Offset(TkText, state), 0},
    {TK_CONFIG_INT, "-width", "width", "Width",
	DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0},
    {TK_CONFIG_UID, "-wrap", "wrap", "Wrap",
	DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

/*
 * The following definition specifies the maximum number of characters
 * needed in a string to hold a position specifier.
 */

#define POS_CHARS 30

/*
 * Tk_Uid's used to represent text states:
 */

Tk_Uid tkTextCharUid = NULL;
Tk_Uid tkTextDisabledUid = NULL;
Tk_Uid tkTextNoneUid = NULL;
Tk_Uid tkTextNormalUid = NULL;
Tk_Uid tkTextWordUid = NULL;

/*
 * Forward declarations for procedures defined later in this file:
 */

static int		ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
			    TkText *textPtr, int argc, char **argv, int flags));
static void		DeleteChars _ANSI_ARGS_((TkText *textPtr, int line1,
			    int ch1, int line2, int ch2));
static void		DestroyText _ANSI_ARGS_((ClientData clientData));
static void		InsertChars _ANSI_ARGS_((TkText *textPtr, int line,
			    int ch, char *string));
static void		TextBlinkProc _ANSI_ARGS_((ClientData clientData));
static void		TextEventProc _ANSI_ARGS_((ClientData clientData,
			    XEvent *eventPtr));
static int		TextFetchSelection _ANSI_ARGS_((ClientData clientData,
			    int offset, char *buffer, int maxBytes));
static void		TextFocusProc _ANSI_ARGS_((ClientData clientData,
			    int gotFocus));
static int		TextMarkCmd _ANSI_ARGS_((TkText *textPtr,
			    Tcl_Interp *interp, int argc, char **argv));
static int		TextScanCmd _ANSI_ARGS_((TkText *textPtr,
			    Tcl_Interp *interp, int argc, char **argv));
static int		TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, int argc, char **argv));

/*
 *--------------------------------------------------------------
 *
 * Tk_TextCmd --
 *
 *	This procedure is invoked to process the "text" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
Tk_TextCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				 * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    Tk_Window new;
    register TkText *textPtr;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " pathName ?options?\"", (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Perform once-only initialization:
     */

    if (tkTextNormalUid == NULL) {
	tkTextCharUid = Tk_GetUid("char");
	tkTextDisabledUid = Tk_GetUid("disabled");
	tkTextNoneUid = Tk_GetUid("none");
	tkTextNormalUid = Tk_GetUid("normal");
	tkTextWordUid = Tk_GetUid("word");
    }

    /*
     * Create the window.
     */

    new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if (new == NULL) {
	return TCL_ERROR;
    }

    textPtr = (TkText *) ckalloc(sizeof(TkText));
    textPtr->tkwin = new;
    textPtr->interp = interp;
    textPtr->tree = TkBTreeCreate();
    Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
    textPtr->numTags = 0;
    Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
    textPtr->state = tkTextNormalUid;
    textPtr->border = NULL;
    textPtr->cursor = None;
    textPtr->fgColor = NULL;
    textPtr->fontPtr = NULL;
    textPtr->prevWidth = Tk_Width(new);
    textPtr->prevHeight = Tk_Height(new);
    textPtr->topLinePtr = NULL;
    TkTextCreateDInfo(textPtr);
    TkTextSetView(textPtr, 0, 0);
    textPtr->selBorder = NULL;
    textPtr->selFgColorPtr = NULL;
    textPtr->exportSelection = 1;
    textPtr->selOffset = -1;
    textPtr->insertAnnotPtr = NULL;
    textPtr->insertBorder = NULL;
    textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
    textPtr->bindingTable = NULL;
    textPtr->pickEvent.type = LeaveNotify;
    textPtr->yScrollCmd = NULL;
    textPtr->scanMarkLine = 0;
    textPtr->scanMarkY = 0;
    textPtr->flags = 0;

    /*
     * Create the "sel" tag and the "current" and "insert" marks.
     */

    textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel");
    textPtr->selTagPtr->relief = TK_RELIEF_RAISED;
    textPtr->currentAnnotPtr = TkTextSetMark(textPtr, "current", 0, 0);
    textPtr->insertAnnotPtr = TkTextSetMark(textPtr, "insert", 0, 0);

    Tk_SetClass(new, "Text");
    Tk_CreateEventHandler(textPtr->tkwin, ExposureMask|StructureNotifyMask,
	    TextEventProc, (ClientData) textPtr);
    Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
	    |ButtonPressMask|ButtonReleaseMask|EnterWindowMask
	    |LeaveWindowMask|PointerMotionMask, TkTextBindProc,
	    (ClientData) textPtr);
    Tk_CreateSelHandler(textPtr->tkwin, XA_STRING, TextFetchSelection,
	    (ClientData) textPtr, XA_STRING);
    Tcl_CreateCommand(interp, Tk_PathName(textPtr->tkwin),
	    TextWidgetCmd, (ClientData) textPtr, (void (*)()) NULL);
    if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
	Tk_DestroyWindow(textPtr->tkwin);
	return TCL_ERROR;
    }
    Tk_CreateFocusHandler(textPtr->tkwin, TextFocusProc, (ClientData) textPtr);
    interp->result = Tk_PathName(textPtr->tkwin);

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * TextWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a text widget.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
TextWidgetCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    register TkText *textPtr = (TkText *) clientData;
    int result = TCL_OK;
    int length;
    char c;
    int line1, line2, ch1, ch2;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " option ?arg arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData) textPtr);
    c = argv[1][0];
    length = strlen(argv[1]);
    if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
	    && (length >= 3)) {
	int less, equal, greater, value;
	char *p;

	if (argc != 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " compare index1 op index2\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if ((TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK)
		|| (TkTextGetIndex(interp, textPtr, argv[4], &line2, &ch2)
		!= TCL_OK)) {
	    result = TCL_ERROR;
	    goto done;
	}
	less = equal = greater = 0;
	if (line1 < line2) {
	    less = 1;
	} else if (line1 > line2) {
	    greater = 1;
	} else {
	    if (ch1 < ch2) {
		less = 1;
	    } else if (ch1 > ch2) {
		greater = 1;
	    } else {
		equal = 1;
	    }
	}
	p = argv[3];
	if (p[0] == '<') {
		value = less;
	    if ((p[1] == '=') && (p[2] == 0)) {
		value = less || equal;
	    } else if (p[1] != 0) {
		compareError:
		Tcl_AppendResult(interp, "bad comparison operator \"",
			argv[3], "\": must be <, <=, ==, >=, >, or !=",
			(char *) NULL);
		result = TCL_ERROR;
		goto done;
	    }
	} else if (p[0] == '>') {
		value = greater;
	    if ((p[1] == '=') && (p[2] == 0)) {
		value = greater || equal;
	    } else if (p[1] != 0) {
		goto compareError;
	    }
	} else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
	    value = equal;
	} else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
	    value = !equal;
	} else {
	    goto compareError;
	}
	interp->result = (value) ? "1" : "0";
    } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
	    && (length >= 3)) {
	if (argc == 2) {
	    result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
		    (char *) textPtr, (char *) NULL, 0);
	} else if (argc == 3) {
	    result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
		    (char *) textPtr, argv[2], 0);
	} else {
	    result = ConfigureText(interp, textPtr, argc-2, argv+2,
		    TK_CONFIG_ARGV_ONLY);
	}
    } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
	    && (length >= 3)) {
	if (argc > 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " debug ?on|off?\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if (argc == 2) {
	    interp->result = (tkBTreeDebug) ? "on" : "off";
	} else {
	    if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
		result = TCL_ERROR;
		goto done;
	    }
	}
    } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
	    && (length >= 3)) {
	if ((argc != 3) && (argc != 4)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " delete index1 ?index2?\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (argc == 3) {
	    line2 = line1;
	    ch2 = ch1+1;
	} else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
		!= TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (textPtr->state == tkTextNormalUid) {
	    DeleteChars(textPtr, line1, ch1, line2, ch2);
	}
    } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
	register TkTextLine *linePtr;

	if ((argc != 3) && (argc != 4)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " get index1 ?index2?\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (argc == 3) {
	    line2 = line1;
	    ch2 = ch1+1;
	} else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
		!= TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (line1 < 0) {
	    line1 = 0;
	    ch1 = 0;
	}
	for (linePtr = TkBTreeFindLine(textPtr->tree, line1);
		(linePtr != NULL) && (line1 <= line2);
		linePtr = TkBTreeNextLine(linePtr), line1++, ch1 = 0) {
	    int savedChar, last;

	    if (line1 == line2) {
		last = ch2;
		if (last > linePtr->numBytes) {
		    last = linePtr->numBytes;
		}
	    } else {
		last = linePtr->numBytes;
	    }
	    if (ch1 >= last) {
		continue;
	    }
	    savedChar = linePtr->bytes[last];
	    linePtr->bytes[last] = 0;
	    Tcl_AppendResult(interp, linePtr->bytes+ch1, (char *) NULL);
	    linePtr->bytes[last] = savedChar;
	}
    } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
	    && (length >= 3)) {
	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " index index\"",
		    (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	TkTextPrintIndex(line1, ch1, interp->result);
    } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
	    && (length >= 3)) {
	if (argc != 4) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " insert index chars ?chars ...?\"",
		    (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (textPtr->state == tkTextNormalUid) {
	    InsertChars(textPtr, line1, ch1, argv[3]);
	}
    } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
	result = TextMarkCmd(textPtr, interp, argc, argv);
    } else if ((c == 's') && (strcmp(argv[1], "scan") == 0)) {
	result = TextScanCmd(textPtr, interp, argc, argv);
    } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
	result = TkTextTagCmd(textPtr, interp, argc, argv);
    } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
	int numLines, pickPlace;

	if (argc < 3) {
	    yviewSyntax:
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " yview ?-pickplace? lineNum|index\"",
		    (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	pickPlace = 0;
	if (argv[2][0] == '-') {
	    int switchLength;

	    switchLength = strlen(argv[2]);
	    if ((switchLength >= 2)
		    && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
		pickPlace = 1;
	    }
	}
	if ((pickPlace+3) != argc) {
	    goto yviewSyntax;
	}
	if (Tcl_GetInt(interp, argv[2+pickPlace], &line1) != TCL_OK) {
	    Tcl_ResetResult(interp);
	    if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],
		    &line1, &ch1) != TCL_OK) {
		result = TCL_ERROR;
		goto done;
	    }
	}
	numLines = TkBTreeNumLines(textPtr->tree);
	if (line1 >= numLines) {
	    line1 = numLines-1;
	}
	if (line1 < 0) {
	    line1 = 0;
	}
	TkTextSetView(textPtr, line1, pickPlace);
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
		"\":  must be compare, configure, debug, delete, get, ",
		"index, insert, mark, scan, tag, or yview",
		(char *) NULL);
	result = TCL_ERROR;
    }

    done:
    Tk_Release((ClientData) textPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyText --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of a text at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the text is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyText(clientData)
    ClientData clientData;	/* Info about text widget. */
{
    register TkText *textPtr = (TkText *) clientData;
    Tcl_HashSearch search;
    Tcl_HashEntry *hPtr;
    TkTextTag *tagPtr;

    TkBTreeDestroy(textPtr->tree);
    for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
	tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
	TkTextFreeTag(tagPtr);
    }
    Tcl_DeleteHashTable(&textPtr->tagTable);
    for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
	ckfree((char *) Tcl_GetHashValue(hPtr));
    }
    Tcl_DeleteHashTable(&textPtr->markTable);
    if (textPtr->border != NULL) {
	Tk_Free3DBorder(textPtr->border);
    }
    if (textPtr->cursor != None) {
	Tk_FreeCursor(textPtr->cursor);
    }
    if (textPtr->fgColor != NULL) {
	Tk_FreeColor(textPtr->fgColor);
    }
    if (textPtr->fontPtr != NULL) {
	Tk_FreeFontStruct(textPtr->fontPtr);
    }
    TkTextFreeDInfo(textPtr);

    /*
     * NOTE: do NOT free up selBorder or selFgColorPtr:  they are
     * duplicates of information in the "sel" tag, which was freed
     * up as part of deleting the tags above.
     */

    if (textPtr->insertBorder != NULL) {
	Tk_Free3DBorder(textPtr->insertBorder);
    }
    if (textPtr->insertBlinkHandler != NULL) {
	Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
    }
    if (textPtr->bindingTable != NULL) {
	Tk_DeleteBindingTable(textPtr->bindingTable);
    }
    if (textPtr->yScrollCmd != NULL) {
	ckfree(textPtr->yScrollCmd);
    }
    ckfree((char *) textPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureText --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or
 *	reconfigure) a text widget.
 *
 * Results:
 *	The return value is 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 textPtr;  old resources get freed, if there
 *	were any.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureText(interp, textPtr, argc, argv, flags)
    Tcl_Interp *interp;		/* Used for error reporting. */
    register TkText *textPtr;	/* Information about widget;  may or may
				 * not already have values for some fields. */
    int argc;			/* Number of valid entries in argv. */
    char **argv;		/* Arguments. */
    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
{
    int oldExport = textPtr->exportSelection;
    int charWidth, charHeight;

    if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
	    argc, argv, (char *) textPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * A few other options also need special processing, such as parsing
     * the geometry and setting the background from a 3-D border.
     */

    if ((textPtr->state != tkTextNormalUid)
	    && (textPtr->state != tkTextDisabledUid)) {
	Tcl_AppendResult(interp, "bad state value \"", textPtr->state,
		"\":  must be normal or disabled", (char *) NULL);
	textPtr->state = tkTextNormalUid;
	return TCL_ERROR;
    }

    if ((textPtr->wrapMode != tkTextCharUid)
	    && (textPtr->wrapMode != tkTextNoneUid)
	    && (textPtr->wrapMode != tkTextWordUid)) {
	Tcl_AppendResult(interp, "bad wrap mode \"", textPtr->state,
		"\":  must be char, none, or word", (char *) NULL);
	textPtr->wrapMode = tkTextCharUid;
	return TCL_ERROR;
    }

    Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
    Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
    Tk_GeometryRequest(textPtr->tkwin, 200, 100);

    /*
     * Make sure that configuration options are properly mirrored
     * between the widget record and the "sel" tags.  NOTE: we don't
     * have to free up information during the mirroring;  old
     * information was freed when it was replaced in the widget
     * record.
     */

    textPtr->selTagPtr->border = textPtr->selBorder;
    textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
    textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;

    /*
     * Claim the selection if we've suddenly started exporting it and there
     * are tagged characters.
     */

    if (textPtr->exportSelection && (!oldExport)) {
	TkTextSearch search;

	TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
		0, textPtr->selTagPtr, &search);
	if (TkBTreeNextTag(&search)) {
	    Tk_OwnSelection(textPtr->tkwin, TkTextLostSelection,
		    (ClientData) textPtr);
	    textPtr->flags |= GOT_SELECTION;
	}
    }

    /*
     * Register the desired geometry for the window, and arrange for
     * the window to be redisplayed.
     */

    if (textPtr->width <= 0) {
	textPtr->width = 1;
    }
    if (textPtr->height <= 0) {
	textPtr->height = 1;
    }
    charWidth = XTextWidth(textPtr->fontPtr, "0", 1);
    charHeight = (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
    Tk_GeometryRequest(textPtr->tkwin,
	    textPtr->width * charWidth + 2*textPtr->borderWidth
		    + 2*textPtr->padX,
	    textPtr->height * charHeight + 2*textPtr->borderWidth
		    + 2*textPtr->padX);
    Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
    if (textPtr->setGrid) {
	Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
		charWidth, charHeight);
    }

    TkTextRelayoutWindow(textPtr);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * TextEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher on
 *	structure changes to a text.  For texts with 3D
 *	borders, this procedure is also invoked for exposures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

static void
TextEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    register XEvent *eventPtr;	/* Information about event. */
{
    register TkText *textPtr = (TkText *) clientData;

    if (eventPtr->type == Expose) {
	TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
		eventPtr->xexpose.y, eventPtr->xexpose.width,
		eventPtr->xexpose.height);
    } else if (eventPtr->type == ConfigureNotify) {
	if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
		|| (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
	    TkTextRelayoutWindow(textPtr);
	}
    } else if (eventPtr->type == DestroyNotify) {
	Tcl_DeleteCommand(textPtr->interp, Tk_PathName(textPtr->tkwin));
	textPtr->tkwin = NULL;
	Tk_EventuallyFree((ClientData) textPtr, DestroyText);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * InsertChars --
 *
 *	This procedure implements most of the functionality of the
 *	"insert" widget command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The characters in "string" get added to the text just before
 *	the character indicated by "line" and "ch".
 *
 *----------------------------------------------------------------------
 */

static void
InsertChars(textPtr, line, ch, string)
    TkText *textPtr;		/* Overall information about text widget. */
    int line, ch;		/* Identifies character just before which
				 * new information is to be inserted. */
    char *string;		/* Null-terminated string containing new
				 * information to add to text. */
{
    register TkTextLine *linePtr;

    /*
     * Locate the line where the insertion will occur.
     */

    linePtr = TkTextRoundIndex(textPtr, &line, &ch);

    /*
     * Notify the display module that lines are about to change, then do
     * the insertion.
     */

    TkTextLinesChanged(textPtr, line, line);
    TkBTreeInsertChars(textPtr->tree, linePtr, ch, string);

    /*
     * If the line containing the insertion point was textPtr->topLinePtr,
     * we must reset this pointer since the line structure was re-allocated.
     */

    if (linePtr == textPtr->topLinePtr) {
	TkTextSetView(textPtr, line, 0);
    }

    /*
     * Invalidate any selection retrievals in progress.
     */

    textPtr->selOffset = -1;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteChars --
 *
 *	This procedure implements most of the functionality of the
 *	"delete" widget command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
DeleteChars(textPtr, line1, ch1, line2, ch2)
    TkText *textPtr;		/* Overall information about text widget. */
    int line1, ch1;		/* Position of first character to delete. */
    int line2, ch2;		/* Position of character just after last
				 * one to delete. */
{
    register TkTextLine *line1Ptr, *line2Ptr;
    int numLines, topLine;

    /*
     * The loop below is needed because a LeaveNotify event may be
     * generated on the current charcter if it's about to be deleted.
     * If this happens, then the bindings that trigger could modify
     * the text, invalidating the range information computed here.
     * So, go back and recompute all the range information after
     * synthesizing a leave event.
     */

    while (1) {

	/*
	 * Locate the starting and ending lines for the deletion and adjust
	 * the endpoints if necessary to ensure that they are within valid
	 * ranges.  Adjust the deletion range if necessary to ensure that the
	 * text (and each invidiual line) always ends in a newline.
	 */
    
	numLines = TkBTreeNumLines(textPtr->tree);
	line1Ptr = TkTextRoundIndex(textPtr, &line1, &ch1);
	if (line2 < 0) {
	    return;
	} else if (line2 >= numLines) {
	    line2 = numLines-1;
	    line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
	    ch2 = line2Ptr->numBytes;
	} else {
	    line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
	    if (ch2 < 0) {
		ch2 = 0;
	    }
	}
    
	/*
	 * If the deletion range ends after the last character of a line,
	 * do one of three things:
	 *
	 * (a) if line2Ptr isn't the last line of the text, just adjust the
	 *     ending point to be just before the 0th character of the next
	 *     line.
	 * (b) if ch1 is at the beginning of a line, then adjust line1Ptr and
	 *     ch1 to point just after the last character of the previous line.
	 * (c) otherwise, adjust ch2 so the final newline isn't deleted.
	 */
    
	if (ch2 >= line2Ptr->numBytes) {
	    if (line2 < (numLines-1)) {
		line2++;
		line2Ptr = TkBTreeNextLine(line2Ptr);
		ch2 = 0;
	    } else {
		ch2 = line2Ptr->numBytes-1;
		if ((ch1 == 0) && (line1 > 0)) {
		    line1--;
		    line1Ptr = TkBTreeFindLine(textPtr->tree, line1);
		    ch1 = line1Ptr->numBytes;
		    ch2 = line2Ptr->numBytes;
		} else {
		    ch2 = line2Ptr->numBytes-1;
		}
	    }
	}

	if ((line1 > line2) || ((line1 == line2) && (ch1 >= ch2))) {
	    return;
	}

	/*
	 * If the current character is within the range being deleted,
	 * unpick it and synthesize a leave event for its tags, then
	 * go back and recompute the range ends.
	 */

	if (!(textPtr->flags & IN_CURRENT)) {
	    break;
	}
	if ((textPtr->currentAnnotPtr->linePtr == line1Ptr)
		&& (textPtr->currentAnnotPtr->ch < ch1)) {
	    break;
	}
	if ((textPtr->currentAnnotPtr->linePtr == line2Ptr)
		&& (textPtr->currentAnnotPtr->ch >= ch2)) {
	    break;
	}
	if (line2 > (line1+1)) {
	    int currentLine;

	    currentLine = TkBTreeLineIndex(textPtr->currentAnnotPtr->linePtr);
	    if ((currentLine <= line1) || (currentLine >= line2)) {
		break;
	    }
	}
	TkTextUnpickCurrent(textPtr);
    }

    /*
     * Tell the display what's about to happen so it can discard
     * obsolete display information, then do the deletion.  Also,
     * check to see if textPtr->topLinePtr is in the range of
     * characters deleted.  If so, call the display module to reset
     * it after doing the deletion.
     */

    topLine = TkBTreeLineIndex(textPtr->topLinePtr);
    TkTextLinesChanged(textPtr, line1, line2);
    TkBTreeDeleteChars(textPtr->tree, line1Ptr, ch1, line2Ptr, ch2);
    if ((topLine >= line1) && (topLine <= line2)) {
	numLines = TkBTreeNumLines(textPtr->tree);
	TkTextSetView(textPtr, (line1 > (numLines-1)) ? (numLines-1) : line1,
		0);
    }

    /*
     * Invalidate any selection retrievals in progress.
     */

    textPtr->selOffset = -1;
}

/*
 *----------------------------------------------------------------------
 *
 * TextFetchSelection --
 *
 *	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
 *	NULL-terminated string containing part or all of the selection,
 *	as given by offset and maxBytes.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
TextFetchSelection(clientData, offset, buffer, maxBytes)
    ClientData clientData;		/* Information about text 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. */
{
    register TkText *textPtr = (TkText *) clientData;
    register TkTextLine *linePtr;
    int count, chunkSize;
    TkTextSearch search;

    if (!textPtr->exportSelection) {
	return -1;
    }

    /*
     * Find the beginning of the next range of selected text.  Note:  if
     * the selection is being retrieved in multiple pieces (offset != 0)
     * and some modification has been made to the text that affects the
     * selection (textPtr->selOffset != offset) then reject the selection
     * request (make 'em start over again).
     */

    if (offset == 0) {
	textPtr->selLine = 0;
	textPtr->selCh = 0;
	textPtr->selOffset = 0;
    } else if (textPtr->selOffset != offset) {
	return 0;
    }
    TkBTreeStartSearch(textPtr->tree, textPtr->selLine, textPtr->selCh+1,
	    TkBTreeNumLines(textPtr->tree), 0, textPtr->selTagPtr, &search);
    if (!TkBTreeCharTagged(search.linePtr, textPtr->selCh,
	    textPtr->selTagPtr)) {
	if (!TkBTreeNextTag(&search)) {
	    if (offset == 0) {
		return -1;
	    } else {
		return 0;
	    }
	}
	textPtr->selLine = search.line1;
	textPtr->selCh = search.ch1;
    }

    /*
     * Each iteration through the outer loop below scans one selected range.
     * Each iteration through the nested loop scans one line in the
     * selected range.
     */

    count = 0;
    while (1) {
	linePtr = search.linePtr;

	/*
	 * Find the end of the current range of selected text.
	 */

	if (!TkBTreeNextTag(&search)) {
	    panic("TextFetchSelection couldn't find end of range");
	}

	/*
	 * Copy information from text lines into the buffer until
	 * either we run out of space in the buffer or we get to
	 * the end of this range of text.
	 */

	while (1) {
	    chunkSize = ((linePtr == search.linePtr) ? search.ch1
		    : linePtr->numBytes) - textPtr->selCh;
	    if (chunkSize > maxBytes) {
		chunkSize = maxBytes;
	    }
	    memcpy((VOID *) buffer, (VOID *) (linePtr->bytes + textPtr->selCh),
		    chunkSize);
	    buffer += chunkSize;
	    maxBytes -= chunkSize;
	    count += chunkSize;
	    textPtr->selOffset += chunkSize;
	    if (maxBytes == 0) {
		textPtr->selCh += chunkSize;
		goto done;
	    }
	    if (linePtr == search.linePtr) {
		break;
	    }
	    textPtr->selCh = 0;
	    textPtr->selLine++;
	    linePtr = TkBTreeNextLine(linePtr);
	}

	/*
	 * Find the beginning of the next range of selected text.
	 */

	if (!TkBTreeNextTag(&search)) {
	    break;
	}
	textPtr->selLine = search.line1;
	textPtr->selCh = search.ch1;
    }

    done:
    *buffer = 0;
    return count;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextLostSelection --
 *
 *	This procedure is called back by Tk when the selection is
 *	grabbed away from a text widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The "sel" tag is cleared from the window.
 *
 *----------------------------------------------------------------------
 */

void
TkTextLostSelection(clientData)
    ClientData clientData;		/* Information about text widget. */
{
    register TkText *textPtr = (TkText *) clientData;

    if (!textPtr->exportSelection) {
	return;
    }

    /*
     * Just remove the "sel" tag from everything in the widget.
     */

    TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree),
	    0, textPtr->selTagPtr, 1);
    TkBTreeTag(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
	    0, textPtr->selTagPtr, 0);
    textPtr->flags &= ~GOT_SELECTION;
}

/*
 *--------------------------------------------------------------
 *
 * TextMarkCmd --
 *
 *	This procedure is invoked to process the "mark" options of
 *	the widget command for text widgets. See the user documentation
 *	for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
TextMarkCmd(textPtr, interp, argc, argv)
    register TkText *textPtr;	/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings.  Someone else has already
				 * parsed this command enough to know that
				 * argv[1] is "mark". */
{
    int length, line, ch, i;
    char c;
    Tcl_HashEntry *hPtr;
    TkAnnotation *markPtr;
    Tcl_HashSearch search;

    if (argc < 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " mark names\"", (char *) NULL);
	    return TCL_ERROR;
	}
	for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
		hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
	    Tcl_AppendElement(interp,
		    Tcl_GetHashKey(&textPtr->markTable, hPtr), 0);
	}
    } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
	if (argc != 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " mark set markName index\"", (char *) NULL);
	    return TCL_ERROR;
	}
	if (TkTextGetIndex(interp, textPtr, argv[4], &line, &ch) != TCL_OK) {
	    return TCL_ERROR;
	}
	TkTextSetMark(textPtr, argv[3], line, ch);
    } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
	if (argc < 4) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " mark unset markName ?markName ...?\"",
		    (char *) NULL);
	    return TCL_ERROR;
	}
	for (i = 3; i < argc; i++) {
	    hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
	    if (hPtr != NULL) {
		markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
		if (markPtr == textPtr->insertAnnotPtr) {
		    interp->result = "can't delete \"insert\" mark";
		    return TCL_ERROR;
		}
		if (markPtr == textPtr->currentAnnotPtr) {
		    interp->result = "can't delete \"current\" mark";
		    return TCL_ERROR;
		}
		TkBTreeRemoveAnnotation(markPtr);
		Tcl_DeleteHashEntry(hPtr);
		ckfree((char *) markPtr);
	    }
	}
    } else {
	Tcl_AppendResult(interp, "bad mark option \"", argv[2],
		"\":  must be names, set, or unset",
		(char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextSetMark --
 *
 *	Set a mark to a particular position, creating a new mark if
 *	one doesn't already exist.
 *
 * Results:
 *	The return value is a pointer to the mark that was just set.
 *
 * Side effects:
 *	A new mark is created, or an existing mark is moved.
 *
 *----------------------------------------------------------------------
 */

TkAnnotation *
TkTextSetMark(textPtr, name, line, ch)
    TkText *textPtr;		/* Text widget in which to create mark. */
    char *name;			/* Name of mark to set. */
    int line;			/* Index of line at which to place mark. */
    int ch;			/* Index of character within line at which
				 * to place mark. */
{
    Tcl_HashEntry *hPtr;
    TkAnnotation *markPtr;
    int new;

    hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
    markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
    if (!new) {
	/*
	 * If this is the insertion point that's being moved, be sure
	 * to force a display update at the old position.
	 */

	if (markPtr == textPtr->insertAnnotPtr) {
	    int oldLine;

	    oldLine = TkBTreeLineIndex(markPtr->linePtr);
	    TkTextLinesChanged(textPtr, oldLine, oldLine);
	}
	TkBTreeRemoveAnnotation(markPtr);
    } else {
	markPtr = (TkAnnotation *) ckalloc(sizeof(TkAnnotation));
	markPtr->type = TK_ANNOT_MARK;
	markPtr->info.hPtr = hPtr;
	Tcl_SetHashValue(hPtr, markPtr);
    }
    if (line < 0) {
	line = 0;
	markPtr->ch = 0;
    } else if (ch < 0) {
	markPtr->ch = 0;
    } else {
	markPtr->ch = ch;
    }
    markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
    if (markPtr->linePtr == NULL) {
	line = TkBTreeNumLines(textPtr->tree)-1;
	markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
	markPtr->ch = markPtr->linePtr->numBytes-1;
    } else {
	if (markPtr->ch >= markPtr->linePtr->numBytes) {
	    TkTextLine *nextLinePtr;

	    nextLinePtr = TkBTreeNextLine(markPtr->linePtr);
	    if (nextLinePtr == NULL) {
		markPtr->ch = markPtr->linePtr->numBytes-1;
	    } else {
		markPtr->linePtr = nextLinePtr;
		line++;
		markPtr->ch = 0;
	    }
	}
    }
    TkBTreeAddAnnotation(markPtr);

    /*
     * If the mark is the insertion cursor, then update the screen at the
     * mark's new location.
     */

    if (markPtr == textPtr->insertAnnotPtr) {
	TkTextLinesChanged(textPtr, line, line);
    }
    return markPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TextBlinkProc --
 *
 *	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
TextBlinkProc(clientData)
    ClientData clientData;	/* Pointer to record describing text. */
{
    register TkText *textPtr = (TkText *) clientData;
    int lineNum;

    if (!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
	return;
    }
    if (textPtr->flags & INSERT_ON) {
	textPtr->flags &= ~INSERT_ON;
	textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
		textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
    } else {
	textPtr->flags |= INSERT_ON;
	textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
		textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
    }
    lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
    TkTextLinesChanged(textPtr, lineNum, lineNum);
}

/*
 *----------------------------------------------------------------------
 *
 * TextFocusProc --
 *
 *	This procedure is called whenever the entry gets or loses the
 *	input focus.  It's also called whenever the window is reconfigured
 *	while it has the focus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cursor gets turned on or off.
 *
 *----------------------------------------------------------------------
 */

static void
TextFocusProc(clientData, gotFocus)
    ClientData clientData;	/* Pointer to structure describing text. */
    int gotFocus;		/* 1 means window is getting focus, 0 means
				 * it's losing it. */
{
    register TkText *textPtr = (TkText *) clientData;
    int lineNum;

    Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
    if (gotFocus) {
	textPtr->flags |= GOT_FOCUS | INSERT_ON;
	if (textPtr->insertOffTime != 0) {
	    textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
		    textPtr->insertOnTime, TextBlinkProc,
		    (ClientData) textPtr);
	}
    } else {
	textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
	textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
    }
    lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
    TkTextLinesChanged(textPtr, lineNum, lineNum);
}

/*
 *--------------------------------------------------------------
 *
 * TextScanCmd --
 *
 *	This procedure is invoked to process the "scan" options of
 *	the widget command for text widgets. See the user documentation
 *	for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
TextScanCmd(textPtr, interp, argc, argv)
    register TkText *textPtr;	/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings.  Someone else has already
				 * parsed this command enough to know that
				 * argv[1] is "tag". */
{
    int length, y, line, lastLine;
    char c;

    if (argc != 4) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " scan mark|dragto y\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
	return TCL_ERROR;
    }
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
	/*
	 * Amplify the difference between the current y position and the
	 * mark position to compute how many lines up or down the view
	 * should shift, then update the mark position to correspond to
	 * the new view.  If we run off the top or bottom of the text,
	 * reset the mark point so that the current position continues
	 * to correspond to the edge of the window.  This means that the
	 * picture will start dragging as soon as the mouse reverses
	 * direction (without this reset, might have to slide mouse a
	 * long ways back before the picture starts moving again).
	 */

	line = textPtr->scanMarkLine + (10*(textPtr->scanMarkY - y))
		/ (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
	lastLine = TkBTreeNumLines(textPtr->tree) - 1;
	if (line < 0) {
	    textPtr->scanMarkLine = line = 0;
	    textPtr->scanMarkY = y;
	} else if (line > lastLine) {
	    textPtr->scanMarkLine = line = lastLine;
	    textPtr->scanMarkY = y;
	}
	TkTextSetView(textPtr, line, 0);
    } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
	textPtr->scanMarkLine = TkBTreeLineIndex(textPtr->topLinePtr);
	textPtr->scanMarkY = y;
    } else {
	Tcl_AppendResult(interp, "bad scan option \"", argv[2],
		"\":  must be mark or dragto", (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}