home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkText.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  44.2 KB  |  1,475 lines

  1. /* 
  2.  * tkText.c --
  3.  *
  4.  *    This module provides a big chunk of the implementation of
  5.  *    multi-line editable text widgets for Tk.  Among other things,
  6.  *    it provides the Tcl command interfaces to text widgets and
  7.  *    the display code.  The B-tree representation of text is
  8.  *    implemented elsewhere.
  9.  *
  10.  * Copyright (c) 1992-1993 The Regents of the University of California.
  11.  * All rights reserved.
  12.  *
  13.  * Permission is hereby granted, without written agreement and without
  14.  * license or royalty fees, to use, copy, modify, and distribute this
  15.  * software and its documentation for any purpose, provided that the
  16.  * above copyright notice and the following two paragraphs appear in
  17.  * all copies of this software.
  18.  * 
  19.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  20.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  21.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  22.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23.  *
  24.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  25.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  26.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  27.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  28.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  29.  */
  30.  
  31. #ifndef lint
  32. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkText.c,v 1.29 93/06/16 17:16:30 ouster Exp $ SPRITE (Berkeley)";
  33. #endif
  34.  
  35. #include "default.h"
  36. #include "tkConfig.h"
  37. #include "tk.h"
  38. #include "tkText.h"
  39.  
  40. /*
  41.  * Information used to parse text configuration options:
  42.  */
  43.  
  44. static Tk_ConfigSpec configSpecs[] = {
  45.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  46.     DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY},
  47.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  48.     DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY},
  49.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  50.     (char *) NULL, 0, 0},
  51.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  52.     (char *) NULL, 0, 0},
  53.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  54.     DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0},
  55.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  56.     DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK},
  57.     {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
  58.     "ExportSelection", DEF_TEXT_EXPORT_SELECTION,
  59.     Tk_Offset(TkText, exportSelection), 0},
  60.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  61.     (char *) NULL, 0, 0},
  62.     {TK_CONFIG_FONT, "-font", "font", "Font",
  63.     DEF_TEXT_FONT, Tk_Offset(TkText, fontPtr), 0},
  64.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  65.     DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0},
  66.     {TK_CONFIG_INT, "-height", "height", "Height",
  67.     DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0},
  68.     {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
  69.     DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0},
  70.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  71.     DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth),
  72.     TK_CONFIG_COLOR_ONLY},
  73.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  74.     DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth),
  75.     TK_CONFIG_MONO_ONLY},
  76.     {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
  77.     DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0},
  78.     {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
  79.     DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0},
  80.     {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
  81.     DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0},
  82.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  83.     DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0},
  84.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  85.     DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0},
  86.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  87.     DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0},
  88.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  89.     DEF_ENTRY_SELECT_COLOR, Tk_Offset(TkText, selBorder),
  90.     TK_CONFIG_COLOR_ONLY},
  91.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  92.     DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder),
  93.     TK_CONFIG_MONO_ONLY},
  94.     {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  95.     DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBorderWidth),
  96.     TK_CONFIG_COLOR_ONLY},
  97.     {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  98.     DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBorderWidth),
  99.     TK_CONFIG_MONO_ONLY},
  100.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  101.     DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr),
  102.     TK_CONFIG_COLOR_ONLY},
  103.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  104.     DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr),
  105.     TK_CONFIG_MONO_ONLY},
  106.     {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
  107.     DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0},
  108.     {TK_CONFIG_UID, "-state", "state", "State",
  109.     DEF_TEXT_STATE, Tk_Offset(TkText, state), 0},
  110.     {TK_CONFIG_INT, "-width", "width", "Width",
  111.     DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0},
  112.     {TK_CONFIG_UID, "-wrap", "wrap", "Wrap",
  113.     DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0},
  114.     {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
  115.     DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd),
  116.     TK_CONFIG_NULL_OK},
  117.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  118.     (char *) NULL, 0, 0}
  119. };
  120.  
  121. /*
  122.  * The following definition specifies the maximum number of characters
  123.  * needed in a string to hold a position specifier.
  124.  */
  125.  
  126. #define POS_CHARS 30
  127.  
  128. /*
  129.  * Tk_Uid's used to represent text states:
  130.  */
  131.  
  132. Tk_Uid tkTextCharUid = NULL;
  133. Tk_Uid tkTextDisabledUid = NULL;
  134. Tk_Uid tkTextNoneUid = NULL;
  135. Tk_Uid tkTextNormalUid = NULL;
  136. Tk_Uid tkTextWordUid = NULL;
  137.  
  138. /*
  139.  * Forward declarations for procedures defined later in this file:
  140.  */
  141.  
  142. static int        ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
  143.                 TkText *textPtr, int argc, char **argv, int flags));
  144. static void        DeleteChars _ANSI_ARGS_((TkText *textPtr, int line1,
  145.                 int ch1, int line2, int ch2));
  146. static void        DestroyText _ANSI_ARGS_((ClientData clientData));
  147. static void        InsertChars _ANSI_ARGS_((TkText *textPtr, int line,
  148.                 int ch, char *string));
  149. static void        TextBlinkProc _ANSI_ARGS_((ClientData clientData));
  150. static void        TextEventProc _ANSI_ARGS_((ClientData clientData,
  151.                 XEvent *eventPtr));
  152. static int        TextFetchSelection _ANSI_ARGS_((ClientData clientData,
  153.                 int offset, char *buffer, int maxBytes));
  154. static int        TextMarkCmd _ANSI_ARGS_((TkText *textPtr,
  155.                 Tcl_Interp *interp, int argc, char **argv));
  156. static int        TextScanCmd _ANSI_ARGS_((TkText *textPtr,
  157.                 Tcl_Interp *interp, int argc, char **argv));
  158. static int        TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
  159.                 Tcl_Interp *interp, int argc, char **argv));
  160.  
  161. /*
  162.  *--------------------------------------------------------------
  163.  *
  164.  * Tk_TextCmd --
  165.  *
  166.  *    This procedure is invoked to process the "text" Tcl command.
  167.  *    See the user documentation for details on what it does.
  168.  *
  169.  * Results:
  170.  *    A standard Tcl result.
  171.  *
  172.  * Side effects:
  173.  *    See the user documentation.
  174.  *
  175.  *--------------------------------------------------------------
  176.  */
  177.  
  178. int
  179. Tk_TextCmd(clientData, interp, argc, argv)
  180.     ClientData clientData;    /* Main window associated with
  181.                  * interpreter. */
  182.     Tcl_Interp *interp;        /* Current interpreter. */
  183.     int argc;            /* Number of arguments. */
  184.     char **argv;        /* Argument strings. */
  185. {
  186.     Tk_Window tkwin = (Tk_Window) clientData;
  187.     Tk_Window new;
  188.     register TkText *textPtr;
  189.  
  190.     if (argc < 2) {
  191.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  192.         argv[0], " pathName ?options?\"", (char *) NULL);
  193.     return TCL_ERROR;
  194.     }
  195.  
  196.     /*
  197.      * Perform once-only initialization:
  198.      */
  199.  
  200.     if (tkTextNormalUid == NULL) {
  201.     tkTextCharUid = Tk_GetUid("char");
  202.     tkTextDisabledUid = Tk_GetUid("disabled");
  203.     tkTextNoneUid = Tk_GetUid("none");
  204.     tkTextNormalUid = Tk_GetUid("normal");
  205.     tkTextWordUid = Tk_GetUid("word");
  206.     }
  207.  
  208.     /*
  209.      * Create the window.
  210.      */
  211.  
  212.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  213.     if (new == NULL) {
  214.     return TCL_ERROR;
  215.     }
  216.  
  217.     textPtr = (TkText *) ckalloc(sizeof(TkText));
  218.     textPtr->tkwin = new;
  219.     textPtr->display = Tk_Display(new);
  220.     textPtr->interp = interp;
  221.     textPtr->tree = TkBTreeCreate();
  222.     Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
  223.     textPtr->numTags = 0;
  224.     Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
  225.     textPtr->state = tkTextNormalUid;
  226.     textPtr->prevWidth = Tk_Width(new);
  227.     textPtr->prevHeight = Tk_Height(new);
  228.     textPtr->topLinePtr = NULL;
  229.     TkTextCreateDInfo(textPtr);
  230.     TkTextSetView(textPtr, 0, 0);
  231.     textPtr->exportSelection = 1;
  232.     textPtr->selOffset = -1;
  233.     textPtr->insertAnnotPtr = NULL;
  234.     textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  235.     textPtr->bindingTable = NULL;
  236.     textPtr->pickEvent.type = LeaveNotify;
  237.     textPtr->scanMarkLine = 0;
  238.     textPtr->scanMarkY = 0;
  239.     textPtr->flags = 0;
  240.  
  241.     /*
  242.      * Create the "sel" tag and the "current" and "insert" marks.
  243.      */
  244.  
  245.     textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel");
  246.     textPtr->selTagPtr->relief = TK_RELIEF_RAISED;
  247.     textPtr->currentAnnotPtr = TkTextSetMark(textPtr, "current", 0, 0);
  248.     textPtr->insertAnnotPtr = TkTextSetMark(textPtr, "insert", 0, 0);
  249.  
  250.     Tk_SetClass(new, "Text");
  251.     Tk_CreateEventHandler(textPtr->tkwin,
  252.         ExposureMask|StructureNotifyMask|FocusChangeMask,
  253.         TextEventProc, (ClientData) textPtr);
  254.     Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
  255.         |ButtonPressMask|ButtonReleaseMask|EnterWindowMask
  256.         |LeaveWindowMask|PointerMotionMask, TkTextBindProc,
  257.         (ClientData) textPtr);
  258.     Tk_CreateSelHandler(textPtr->tkwin, XA_STRING, TextFetchSelection,
  259.         (ClientData) textPtr, XA_STRING);
  260.     Tcl_CreateCommand(interp, Tk_PathName(textPtr->tkwin),
  261.         TextWidgetCmd, (ClientData) textPtr, (void (*)()) NULL);
  262.     if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
  263.     Tk_DestroyWindow(textPtr->tkwin);
  264.     return TCL_ERROR;
  265.     }
  266.     interp->result = Tk_PathName(textPtr->tkwin);
  267.  
  268.     return TCL_OK;
  269. }
  270.  
  271. /*
  272.  *--------------------------------------------------------------
  273.  *
  274.  * TextWidgetCmd --
  275.  *
  276.  *    This procedure is invoked to process the Tcl command
  277.  *    that corresponds to a text widget.  See the user
  278.  *    documentation for details on what it does.
  279.  *
  280.  * Results:
  281.  *    A standard Tcl result.
  282.  *
  283.  * Side effects:
  284.  *    See the user documentation.
  285.  *
  286.  *--------------------------------------------------------------
  287.  */
  288.  
  289. static int
  290. TextWidgetCmd(clientData, interp, argc, argv)
  291.     ClientData clientData;    /* Information about text widget. */
  292.     Tcl_Interp *interp;        /* Current interpreter. */
  293.     int argc;            /* Number of arguments. */
  294.     char **argv;        /* Argument strings. */
  295. {
  296.     register TkText *textPtr = (TkText *) clientData;
  297.     int result = TCL_OK;
  298.     int length;
  299.     char c;
  300.     int line1, line2, ch1, ch2;
  301.  
  302.     if (argc < 2) {
  303.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  304.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  305.     return TCL_ERROR;
  306.     }
  307.     Tk_Preserve((ClientData) textPtr);
  308.     c = argv[1][0];
  309.     length = strlen(argv[1]);
  310.     if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
  311.         && (length >= 3)) {
  312.     int less, equal, greater, value;
  313.     char *p;
  314.  
  315.     if (argc != 5) {
  316.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  317.             argv[0], " compare index1 op index2\"", (char *) NULL);
  318.         result = TCL_ERROR;
  319.         goto done;
  320.     }
  321.     if ((TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK)
  322.         || (TkTextGetIndex(interp, textPtr, argv[4], &line2, &ch2)
  323.         != TCL_OK)) {
  324.         result = TCL_ERROR;
  325.         goto done;
  326.     }
  327.     less = equal = greater = 0;
  328.     if (line1 < line2) {
  329.         less = 1;
  330.     } else if (line1 > line2) {
  331.         greater = 1;
  332.     } else {
  333.         if (ch1 < ch2) {
  334.         less = 1;
  335.         } else if (ch1 > ch2) {
  336.         greater = 1;
  337.         } else {
  338.         equal = 1;
  339.         }
  340.     }
  341.     p = argv[3];
  342.     if (p[0] == '<') {
  343.         value = less;
  344.         if ((p[1] == '=') && (p[2] == 0)) {
  345.         value = less || equal;
  346.         } else if (p[1] != 0) {
  347.         compareError:
  348.         Tcl_AppendResult(interp, "bad comparison operator \"",
  349.             argv[3], "\": must be <, <=, ==, >=, >, or !=",
  350.             (char *) NULL);
  351.         result = TCL_ERROR;
  352.         goto done;
  353.         }
  354.     } else if (p[0] == '>') {
  355.         value = greater;
  356.         if ((p[1] == '=') && (p[2] == 0)) {
  357.         value = greater || equal;
  358.         } else if (p[1] != 0) {
  359.         goto compareError;
  360.         }
  361.     } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
  362.         value = equal;
  363.     } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
  364.         value = !equal;
  365.     } else {
  366.         goto compareError;
  367.     }
  368.     interp->result = (value) ? "1" : "0";
  369.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  370.         && (length >= 3)) {
  371.     if (argc == 2) {
  372.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  373.             (char *) textPtr, (char *) NULL, 0);
  374.     } else if (argc == 3) {
  375.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  376.             (char *) textPtr, argv[2], 0);
  377.     } else {
  378.         result = ConfigureText(interp, textPtr, argc-2, argv+2,
  379.             TK_CONFIG_ARGV_ONLY);
  380.     }
  381.     } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
  382.         && (length >= 3)) {
  383.     if (argc > 3) {
  384.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  385.             argv[0], " debug ?on|off?\"", (char *) NULL);
  386.         result = TCL_ERROR;
  387.         goto done;
  388.     }
  389.     if (argc == 2) {
  390.         interp->result = (tkBTreeDebug) ? "on" : "off";
  391.     } else {
  392.         if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
  393.         result = TCL_ERROR;
  394.         goto done;
  395.         }
  396.     }
  397.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  398.         && (length >= 3)) {
  399.     if ((argc != 3) && (argc != 4)) {
  400.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  401.             argv[0], " delete index1 ?index2?\"", (char *) NULL);
  402.         result = TCL_ERROR;
  403.         goto done;
  404.     }
  405.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  406.         result = TCL_ERROR;
  407.         goto done;
  408.     }
  409.     if (argc == 3) {
  410.         line2 = line1;
  411.         ch2 = ch1+1;
  412.     } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
  413.         != TCL_OK) {
  414.         result = TCL_ERROR;
  415.         goto done;
  416.     }
  417.     if (textPtr->state == tkTextNormalUid) {
  418.         DeleteChars(textPtr, line1, ch1, line2, ch2);
  419.     }
  420.     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
  421.     register TkTextLine *linePtr;
  422.  
  423.     if ((argc != 3) && (argc != 4)) {
  424.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  425.             argv[0], " get index1 ?index2?\"", (char *) NULL);
  426.         result = TCL_ERROR;
  427.         goto done;
  428.     }
  429.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  430.         result = TCL_ERROR;
  431.         goto done;
  432.     }
  433.     if (argc == 3) {
  434.         line2 = line1;
  435.         ch2 = ch1+1;
  436.     } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
  437.         != TCL_OK) {
  438.         result = TCL_ERROR;
  439.         goto done;
  440.     }
  441.     if (line1 < 0) {
  442.         line1 = 0;
  443.         ch1 = 0;
  444.     }
  445.     for (linePtr = TkBTreeFindLine(textPtr->tree, line1);
  446.         (linePtr != NULL) && (line1 <= line2);
  447.         linePtr = TkBTreeNextLine(linePtr), line1++, ch1 = 0) {
  448.         int savedChar, last;
  449.  
  450.         if (line1 == line2) {
  451.         last = ch2;
  452.         if (last > linePtr->numBytes) {
  453.             last = linePtr->numBytes;
  454.         }
  455.         } else {
  456.         last = linePtr->numBytes;
  457.         }
  458.         if (ch1 >= last) {
  459.         continue;
  460.         }
  461.         savedChar = linePtr->bytes[last];
  462.         linePtr->bytes[last] = 0;
  463.         Tcl_AppendResult(interp, linePtr->bytes+ch1, (char *) NULL);
  464.         linePtr->bytes[last] = savedChar;
  465.     }
  466.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  467.         && (length >= 3)) {
  468.     if (argc != 3) {
  469.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  470.             argv[0], " index index\"",
  471.             (char *) NULL);
  472.         result = TCL_ERROR;
  473.         goto done;
  474.     }
  475.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  476.         result = TCL_ERROR;
  477.         goto done;
  478.     }
  479.     TkTextPrintIndex(line1, ch1, interp->result);
  480.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  481.         && (length >= 3)) {
  482.     if (argc != 4) {
  483.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  484.             argv[0], " insert index chars ?chars ...?\"",
  485.             (char *) NULL);
  486.         result = TCL_ERROR;
  487.         goto done;
  488.     }
  489.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  490.         result = TCL_ERROR;
  491.         goto done;
  492.     }
  493.     if (textPtr->state == tkTextNormalUid) {
  494.         InsertChars(textPtr, line1, ch1, argv[3]);
  495.     }
  496.     } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
  497.     result = TextMarkCmd(textPtr, interp, argc, argv);
  498.     } else if ((c == 's') && (strcmp(argv[1], "scan") == 0)) {
  499.     result = TextScanCmd(textPtr, interp, argc, argv);
  500.     } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
  501.     result = TkTextTagCmd(textPtr, interp, argc, argv);
  502.     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
  503.     int numLines, pickPlace;
  504.  
  505.     if (argc < 3) {
  506.         yviewSyntax:
  507.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  508.             argv[0], " yview ?-pickplace? lineNum|index\"",
  509.             (char *) NULL);
  510.         result = TCL_ERROR;
  511.         goto done;
  512.     }
  513.     pickPlace = 0;
  514.     if (argv[2][0] == '-') {
  515.         int switchLength;
  516.  
  517.         switchLength = strlen(argv[2]);
  518.         if ((switchLength >= 2)
  519.             && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
  520.         pickPlace = 1;
  521.         }
  522.     }
  523.     if ((pickPlace+3) != argc) {
  524.         goto yviewSyntax;
  525.     }
  526.     if (Tcl_GetInt(interp, argv[2+pickPlace], &line1) != TCL_OK) {
  527.         Tcl_ResetResult(interp);
  528.         if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],
  529.             &line1, &ch1) != TCL_OK) {
  530.         result = TCL_ERROR;
  531.         goto done;
  532.         }
  533.     }
  534.     numLines = TkBTreeNumLines(textPtr->tree);
  535.     if (line1 >= numLines) {
  536.         line1 = numLines-1;
  537.     }
  538.     if (line1 < 0) {
  539.         line1 = 0;
  540.     }
  541.     TkTextSetView(textPtr, line1, pickPlace);
  542.     } else {
  543.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  544.         "\":  must be compare, configure, debug, delete, get, ",
  545.         "index, insert, mark, scan, tag, or yview",
  546.         (char *) NULL);
  547.     result = TCL_ERROR;
  548.     }
  549.  
  550.     done:
  551.     Tk_Release((ClientData) textPtr);
  552.     return result;
  553. }
  554.  
  555. /*
  556.  *----------------------------------------------------------------------
  557.  *
  558.  * DestroyText --
  559.  *
  560.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  561.  *    to clean up the internal structure of a text at a safe time
  562.  *    (when no-one is using it anymore).
  563.  *
  564.  * Results:
  565.  *    None.
  566.  *
  567.  * Side effects:
  568.  *    Everything associated with the text is freed up.
  569.  *
  570.  *----------------------------------------------------------------------
  571.  */
  572.  
  573. static void
  574. DestroyText(clientData)
  575.     ClientData clientData;    /* Info about text widget. */
  576. {
  577.     register TkText *textPtr = (TkText *) clientData;
  578.     Tcl_HashSearch search;
  579.     Tcl_HashEntry *hPtr;
  580.     TkTextTag *tagPtr;
  581.  
  582.     /*
  583.      * Free up all the stuff that requires special handling, then
  584.      * let Tk_FreeOptions handle all the standard option-related
  585.      * stuff.
  586.      */
  587.  
  588.     TkBTreeDestroy(textPtr->tree);
  589.     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
  590.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  591.     tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  592.     TkTextFreeTag(textPtr, tagPtr);
  593.     }
  594.     Tcl_DeleteHashTable(&textPtr->tagTable);
  595.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  596.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  597.     ckfree((char *) Tcl_GetHashValue(hPtr));
  598.     }
  599.     Tcl_DeleteHashTable(&textPtr->markTable);
  600.     TkTextFreeDInfo(textPtr);
  601.     if (textPtr->insertBlinkHandler != NULL) {
  602.     Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  603.     }
  604.     if (textPtr->bindingTable != NULL) {
  605.     Tk_DeleteBindingTable(textPtr->bindingTable);
  606.     }
  607.  
  608.     /*
  609.      * NOTE: do NOT free up selBorder or selFgColorPtr:  they are
  610.      * duplicates of information in the "sel" tag, which was freed
  611.      * up as part of deleting the tags above.
  612.      */
  613.  
  614.     textPtr->selBorder = NULL;
  615.     textPtr->selFgColorPtr = NULL;
  616.     Tk_FreeOptions(configSpecs, (char *) textPtr, textPtr->display, 0);
  617.     ckfree((char *) textPtr);
  618. }
  619.  
  620. /*
  621.  *----------------------------------------------------------------------
  622.  *
  623.  * ConfigureText --
  624.  *
  625.  *    This procedure is called to process an argv/argc list, plus
  626.  *    the Tk option database, in order to configure (or
  627.  *    reconfigure) a text widget.
  628.  *
  629.  * Results:
  630.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  631.  *    returned, then interp->result contains an error message.
  632.  *
  633.  * Side effects:
  634.  *    Configuration information, such as text string, colors, font,
  635.  *    etc. get set for textPtr;  old resources get freed, if there
  636.  *    were any.
  637.  *
  638.  *----------------------------------------------------------------------
  639.  */
  640.  
  641. static int
  642. ConfigureText(interp, textPtr, argc, argv, flags)
  643.     Tcl_Interp *interp;        /* Used for error reporting. */
  644.     register TkText *textPtr;    /* Information about widget;  may or may
  645.                  * not already have values for some fields. */
  646.     int argc;            /* Number of valid entries in argv. */
  647.     char **argv;        /* Arguments. */
  648.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  649. {
  650.     int oldExport = textPtr->exportSelection;
  651.     int charWidth, charHeight;
  652.  
  653.     if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
  654.         argc, argv, (char *) textPtr, flags) != TCL_OK) {
  655.     return TCL_ERROR;
  656.     }
  657.  
  658.     /*
  659.      * A few other options also need special processing, such as parsing
  660.      * the geometry and setting the background from a 3-D border.
  661.      */
  662.  
  663.     if ((textPtr->state != tkTextNormalUid)
  664.         && (textPtr->state != tkTextDisabledUid)) {
  665.     Tcl_AppendResult(interp, "bad state value \"", textPtr->state,
  666.         "\":  must be normal or disabled", (char *) NULL);
  667.     textPtr->state = tkTextNormalUid;
  668.     return TCL_ERROR;
  669.     }
  670.  
  671.     if ((textPtr->wrapMode != tkTextCharUid)
  672.         && (textPtr->wrapMode != tkTextNoneUid)
  673.         && (textPtr->wrapMode != tkTextWordUid)) {
  674.     Tcl_AppendResult(interp, "bad wrap mode \"", textPtr->state,
  675.         "\":  must be char, none, or word", (char *) NULL);
  676.     textPtr->wrapMode = tkTextCharUid;
  677.     return TCL_ERROR;
  678.     }
  679.  
  680.     Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
  681.     Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
  682.     Tk_GeometryRequest(textPtr->tkwin, 200, 100);
  683.  
  684.     /*
  685.      * Make sure that configuration options are properly mirrored
  686.      * between the widget record and the "sel" tags.  NOTE: we don't
  687.      * have to free up information during the mirroring;  old
  688.      * information was freed when it was replaced in the widget
  689.      * record.
  690.      */
  691.  
  692.     textPtr->selTagPtr->border = textPtr->selBorder;
  693.     textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
  694.     textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
  695.  
  696.     /*
  697.      * Claim the selection if we've suddenly started exporting it and there
  698.      * are tagged characters.
  699.      */
  700.  
  701.     if (textPtr->exportSelection && (!oldExport)) {
  702.     TkTextSearch search;
  703.  
  704.     TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
  705.         0, textPtr->selTagPtr, &search);
  706.     if (TkBTreeNextTag(&search)) {
  707.         Tk_OwnSelection(textPtr->tkwin, TkTextLostSelection,
  708.             (ClientData) textPtr);
  709.         textPtr->flags |= GOT_SELECTION;
  710.     }
  711.     }
  712.  
  713.     /*
  714.      * Register the desired geometry for the window, and arrange for
  715.      * the window to be redisplayed.
  716.      */
  717.  
  718.     if (textPtr->width <= 0) {
  719.     textPtr->width = 1;
  720.     }
  721.     if (textPtr->height <= 0) {
  722.     textPtr->height = 1;
  723.     }
  724.     charWidth = XTextWidth(textPtr->fontPtr, "0", 1);
  725.     charHeight = (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
  726.     Tk_GeometryRequest(textPtr->tkwin,
  727.         textPtr->width * charWidth + 2*textPtr->borderWidth
  728.             + 2*textPtr->padX,
  729.         textPtr->height * charHeight + 2*textPtr->borderWidth
  730.             + 2*textPtr->padY);
  731.     Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
  732.     if (textPtr->setGrid) {
  733.     Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
  734.         charWidth, charHeight);
  735.     }
  736.  
  737.     TkTextRelayoutWindow(textPtr);
  738.     return TCL_OK;
  739. }
  740.  
  741. /*
  742.  *--------------------------------------------------------------
  743.  *
  744.  * TextEventProc --
  745.  *
  746.  *    This procedure is invoked by the Tk dispatcher on
  747.  *    structure changes to a text.  For texts with 3D
  748.  *    borders, this procedure is also invoked for exposures.
  749.  *
  750.  * Results:
  751.  *    None.
  752.  *
  753.  * Side effects:
  754.  *    When the window gets deleted, internal structures get
  755.  *    cleaned up.  When it gets exposed, it is redisplayed.
  756.  *
  757.  *--------------------------------------------------------------
  758.  */
  759.  
  760. static void
  761. TextEventProc(clientData, eventPtr)
  762.     ClientData clientData;    /* Information about window. */
  763.     register XEvent *eventPtr;    /* Information about event. */
  764. {
  765.     register TkText *textPtr = (TkText *) clientData;
  766.     int lineNum;
  767.  
  768.     if (eventPtr->type == Expose) {
  769.     TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
  770.         eventPtr->xexpose.y, eventPtr->xexpose.width,
  771.         eventPtr->xexpose.height);
  772.     } else if (eventPtr->type == ConfigureNotify) {
  773.     if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
  774.         || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
  775.         TkTextRelayoutWindow(textPtr);
  776.     }
  777.     } else if (eventPtr->type == DestroyNotify) {
  778.     Tcl_DeleteCommand(textPtr->interp, Tk_PathName(textPtr->tkwin));
  779.     textPtr->tkwin = NULL;
  780.     Tk_EventuallyFree((ClientData) textPtr, DestroyText);
  781.     } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
  782.     Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  783.     if (eventPtr->type == FocusIn) {
  784.         textPtr->flags |= GOT_FOCUS | INSERT_ON;
  785.         if (textPtr->insertOffTime != 0) {
  786.         textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  787.             textPtr->insertOnTime, TextBlinkProc,
  788.             (ClientData) textPtr);
  789.         }
  790.     } else {
  791.         textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
  792.         textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  793.     }
  794.     lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
  795.     TkTextLinesChanged(textPtr, lineNum, lineNum);
  796.     }
  797. }
  798.  
  799. /*
  800.  *----------------------------------------------------------------------
  801.  *
  802.  * InsertChars --
  803.  *
  804.  *    This procedure implements most of the functionality of the
  805.  *    "insert" widget command.
  806.  *
  807.  * Results:
  808.  *    None.
  809.  *
  810.  * Side effects:
  811.  *    The characters in "string" get added to the text just before
  812.  *    the character indicated by "line" and "ch".
  813.  *
  814.  *----------------------------------------------------------------------
  815.  */
  816.  
  817. static void
  818. InsertChars(textPtr, line, ch, string)
  819.     TkText *textPtr;        /* Overall information about text widget. */
  820.     int line, ch;        /* Identifies character just before which
  821.                  * new information is to be inserted. */
  822.     char *string;        /* Null-terminated string containing new
  823.                  * information to add to text. */
  824. {
  825.     register TkTextLine *linePtr;
  826.  
  827.     /*
  828.      * Locate the line where the insertion will occur.
  829.      */
  830.  
  831.     linePtr = TkTextRoundIndex(textPtr, &line, &ch);
  832.  
  833.     /*
  834.      * Notify the display module that lines are about to change, then do
  835.      * the insertion.
  836.      */
  837.  
  838.     TkTextLinesChanged(textPtr, line, line);
  839.     TkBTreeInsertChars(textPtr->tree, linePtr, ch, string);
  840.  
  841.     /*
  842.      * If the line containing the insertion point was textPtr->topLinePtr,
  843.      * we must reset this pointer since the line structure was re-allocated.
  844.      */
  845.  
  846.     if (linePtr == textPtr->topLinePtr) {
  847.     TkTextSetView(textPtr, line, 0);
  848.     }
  849.  
  850.     /*
  851.      * Invalidate any selection retrievals in progress.
  852.      */
  853.  
  854.     textPtr->selOffset = -1;
  855. }
  856.  
  857. /*
  858.  *----------------------------------------------------------------------
  859.  *
  860.  * DeleteChars --
  861.  *
  862.  *    This procedure implements most of the functionality of the
  863.  *    "delete" widget command.
  864.  *
  865.  * Results:
  866.  *    None.
  867.  *
  868.  * Side effects:
  869.  *    None.
  870.  *
  871.  *----------------------------------------------------------------------
  872.  */
  873.  
  874. static void
  875. DeleteChars(textPtr, line1, ch1, line2, ch2)
  876.     TkText *textPtr;        /* Overall information about text widget. */
  877.     int line1, ch1;        /* Position of first character to delete. */
  878.     int line2, ch2;        /* Position of character just after last
  879.                  * one to delete. */
  880. {
  881.     register TkTextLine *line1Ptr, *line2Ptr;
  882.     int numLines, topLine;
  883.  
  884.     /*
  885.      * The loop below is needed because a LeaveNotify event may be
  886.      * generated on the current charcter if it's about to be deleted.
  887.      * If this happens, then the bindings that trigger could modify
  888.      * the text, invalidating the range information computed here.
  889.      * So, go back and recompute all the range information after
  890.      * synthesizing a leave event.
  891.      */
  892.  
  893.     while (1) {
  894.  
  895.     /*
  896.      * Locate the starting and ending lines for the deletion and adjust
  897.      * the endpoints if necessary to ensure that they are within valid
  898.      * ranges.  Adjust the deletion range if necessary to ensure that the
  899.      * text (and each invidiual line) always ends in a newline.
  900.      */
  901.     
  902.     numLines = TkBTreeNumLines(textPtr->tree);
  903.     line1Ptr = TkTextRoundIndex(textPtr, &line1, &ch1);
  904.     if (line2 < 0) {
  905.         return;
  906.     } else if (line2 >= numLines) {
  907.         line2 = numLines-1;
  908.         line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
  909.         ch2 = line2Ptr->numBytes;
  910.     } else {
  911.         line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
  912.         if (ch2 < 0) {
  913.         ch2 = 0;
  914.         }
  915.     }
  916.     
  917.     /*
  918.      * If the deletion range ends after the last character of a line,
  919.      * do one of three things:
  920.      *
  921.      * (a) if line2Ptr isn't the last line of the text, just adjust the
  922.      *     ending point to be just before the 0th character of the next
  923.      *     line.
  924.      * (b) if ch1 is at the beginning of a line, then adjust line1Ptr and
  925.      *     ch1 to point just after the last character of the previous line.
  926.      * (c) otherwise, adjust ch2 so the final newline isn't deleted.
  927.      */
  928.     
  929.     if (ch2 >= line2Ptr->numBytes) {
  930.         if (line2 < (numLines-1)) {
  931.         line2++;
  932.         line2Ptr = TkBTreeNextLine(line2Ptr);
  933.         ch2 = 0;
  934.         } else {
  935.         ch2 = line2Ptr->numBytes-1;
  936.         if ((ch1 == 0) && (line1 > 0)) {
  937.             line1--;
  938.             line1Ptr = TkBTreeFindLine(textPtr->tree, line1);
  939.             ch1 = line1Ptr->numBytes;
  940.             ch2 = line2Ptr->numBytes;
  941.         } else {
  942.             ch2 = line2Ptr->numBytes-1;
  943.         }
  944.         }
  945.     }
  946.  
  947.     if ((line1 > line2) || ((line1 == line2) && (ch1 >= ch2))) {
  948.         return;
  949.     }
  950.  
  951.     /*
  952.      * If the current character is within the range being deleted,
  953.      * unpick it and synthesize a leave event for its tags, then
  954.      * go back and recompute the range ends.
  955.      */
  956.  
  957.     if (!(textPtr->flags & IN_CURRENT)) {
  958.         break;
  959.     }
  960.     if ((textPtr->currentAnnotPtr->linePtr == line1Ptr)
  961.         && (textPtr->currentAnnotPtr->ch < ch1)) {
  962.         break;
  963.     }
  964.     if ((textPtr->currentAnnotPtr->linePtr == line2Ptr)
  965.         && (textPtr->currentAnnotPtr->ch >= ch2)) {
  966.         break;
  967.     }
  968.     if (line2 > (line1+1)) {
  969.         int currentLine;
  970.  
  971.         currentLine = TkBTreeLineIndex(textPtr->currentAnnotPtr->linePtr);
  972.         if ((currentLine <= line1) || (currentLine >= line2)) {
  973.         break;
  974.         }
  975.     }
  976.     TkTextUnpickCurrent(textPtr);
  977.     }
  978.  
  979.     /*
  980.      * Tell the display what's about to happen so it can discard
  981.      * obsolete display information, then do the deletion.  Also,
  982.      * check to see if textPtr->topLinePtr is in the range of
  983.      * characters deleted.  If so, call the display module to reset
  984.      * it after doing the deletion.
  985.      */
  986.  
  987.     topLine = TkBTreeLineIndex(textPtr->topLinePtr);
  988.     TkTextLinesChanged(textPtr, line1, line2);
  989.     TkBTreeDeleteChars(textPtr->tree, line1Ptr, ch1, line2Ptr, ch2);
  990.     if ((topLine >= line1) && (topLine <= line2)) {
  991.     numLines = TkBTreeNumLines(textPtr->tree);
  992.     TkTextSetView(textPtr, (line1 > (numLines-1)) ? (numLines-1) : line1,
  993.         0);
  994.     }
  995.  
  996.     /*
  997.      * Invalidate any selection retrievals in progress.
  998.      */
  999.  
  1000.     textPtr->selOffset = -1;
  1001. }
  1002.  
  1003. /*
  1004.  *----------------------------------------------------------------------
  1005.  *
  1006.  * TextFetchSelection --
  1007.  *
  1008.  *    This procedure is called back by Tk when the selection is
  1009.  *    requested by someone.  It returns part or all of the selection
  1010.  *    in a buffer provided by the caller.
  1011.  *
  1012.  * Results:
  1013.  *    The return value is the number of non-NULL bytes stored
  1014.  *    at buffer.  Buffer is filled (or partially filled) with a
  1015.  *    NULL-terminated string containing part or all of the selection,
  1016.  *    as given by offset and maxBytes.
  1017.  *
  1018.  * Side effects:
  1019.  *    None.
  1020.  *
  1021.  *----------------------------------------------------------------------
  1022.  */
  1023.  
  1024. static int
  1025. TextFetchSelection(clientData, offset, buffer, maxBytes)
  1026.     ClientData clientData;        /* Information about text widget. */
  1027.     int offset;                /* Offset within selection of first
  1028.                      * character to be returned. */
  1029.     char *buffer;            /* Location in which to place
  1030.                      * selection. */
  1031.     int maxBytes;            /* Maximum number of bytes to place
  1032.                      * at buffer, not including terminating
  1033.                      * NULL character. */
  1034. {
  1035.     register TkText *textPtr = (TkText *) clientData;
  1036.     register TkTextLine *linePtr;
  1037.     int count, chunkSize;
  1038.     TkTextSearch search;
  1039.  
  1040.     if (!textPtr->exportSelection) {
  1041.     return -1;
  1042.     }
  1043.  
  1044.     /*
  1045.      * Find the beginning of the next range of selected text.  Note:  if
  1046.      * the selection is being retrieved in multiple pieces (offset != 0)
  1047.      * and some modification has been made to the text that affects the
  1048.      * selection (textPtr->selOffset != offset) then reject the selection
  1049.      * request (make 'em start over again).
  1050.      */
  1051.  
  1052.     if (offset == 0) {
  1053.     textPtr->selLine = 0;
  1054.     textPtr->selCh = 0;
  1055.     textPtr->selOffset = 0;
  1056.     } else if (textPtr->selOffset != offset) {
  1057.     return 0;
  1058.     }
  1059.     TkBTreeStartSearch(textPtr->tree, textPtr->selLine, textPtr->selCh+1,
  1060.         TkBTreeNumLines(textPtr->tree), 0, textPtr->selTagPtr, &search);
  1061.     if (!TkBTreeCharTagged(search.linePtr, textPtr->selCh,
  1062.         textPtr->selTagPtr)) {
  1063.     if (!TkBTreeNextTag(&search)) {
  1064.         if (offset == 0) {
  1065.         return -1;
  1066.         } else {
  1067.         return 0;
  1068.         }
  1069.     }
  1070.     textPtr->selLine = search.line1;
  1071.     textPtr->selCh = search.ch1;
  1072.     }
  1073.  
  1074.     /*
  1075.      * Each iteration through the outer loop below scans one selected range.
  1076.      * Each iteration through the nested loop scans one line in the
  1077.      * selected range.
  1078.      */
  1079.  
  1080.     count = 0;
  1081.     while (1) {
  1082.     linePtr = search.linePtr;
  1083.  
  1084.     /*
  1085.      * Find the end of the current range of selected text.
  1086.      */
  1087.  
  1088.     if (!TkBTreeNextTag(&search)) {
  1089.         panic("TextFetchSelection couldn't find end of range");
  1090.     }
  1091.  
  1092.     /*
  1093.      * Copy information from text lines into the buffer until
  1094.      * either we run out of space in the buffer or we get to
  1095.      * the end of this range of text.
  1096.      */
  1097.  
  1098.     while (1) {
  1099.         chunkSize = ((linePtr == search.linePtr) ? search.ch1
  1100.             : linePtr->numBytes) - textPtr->selCh;
  1101.         if (chunkSize > maxBytes) {
  1102.         chunkSize = maxBytes;
  1103.         }
  1104.         memcpy((VOID *) buffer, (VOID *) (linePtr->bytes + textPtr->selCh),
  1105.             chunkSize);
  1106.         buffer += chunkSize;
  1107.         maxBytes -= chunkSize;
  1108.         count += chunkSize;
  1109.         textPtr->selOffset += chunkSize;
  1110.         if (maxBytes == 0) {
  1111.         textPtr->selCh += chunkSize;
  1112.         goto done;
  1113.         }
  1114.         if (linePtr == search.linePtr) {
  1115.         break;
  1116.         }
  1117.         textPtr->selCh = 0;
  1118.         textPtr->selLine++;
  1119.         linePtr = TkBTreeNextLine(linePtr);
  1120.     }
  1121.  
  1122.     /*
  1123.      * Find the beginning of the next range of selected text.
  1124.      */
  1125.  
  1126.     if (!TkBTreeNextTag(&search)) {
  1127.         break;
  1128.     }
  1129.     textPtr->selLine = search.line1;
  1130.     textPtr->selCh = search.ch1;
  1131.     }
  1132.  
  1133.     done:
  1134.     *buffer = 0;
  1135.     return count;
  1136. }
  1137.  
  1138. /*
  1139.  *----------------------------------------------------------------------
  1140.  *
  1141.  * TkTextLostSelection --
  1142.  *
  1143.  *    This procedure is called back by Tk when the selection is
  1144.  *    grabbed away from a text widget.
  1145.  *
  1146.  * Results:
  1147.  *    None.
  1148.  *
  1149.  * Side effects:
  1150.  *    The "sel" tag is cleared from the window.
  1151.  *
  1152.  *----------------------------------------------------------------------
  1153.  */
  1154.  
  1155. void
  1156. TkTextLostSelection(clientData)
  1157.     ClientData clientData;        /* Information about text widget. */
  1158. {
  1159.     register TkText *textPtr = (TkText *) clientData;
  1160.  
  1161.     if (!textPtr->exportSelection) {
  1162.     return;
  1163.     }
  1164.  
  1165.     /*
  1166.      * Just remove the "sel" tag from everything in the widget.
  1167.      */
  1168.  
  1169.     TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree),
  1170.         0, textPtr->selTagPtr, 1);
  1171.     TkBTreeTag(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
  1172.         0, textPtr->selTagPtr, 0);
  1173.     textPtr->flags &= ~GOT_SELECTION;
  1174. }
  1175.  
  1176. /*
  1177.  *--------------------------------------------------------------
  1178.  *
  1179.  * TextMarkCmd --
  1180.  *
  1181.  *    This procedure is invoked to process the "mark" options of
  1182.  *    the widget command for text widgets. See the user documentation
  1183.  *    for details on what it does.
  1184.  *
  1185.  * Results:
  1186.  *    A standard Tcl result.
  1187.  *
  1188.  * Side effects:
  1189.  *    See the user documentation.
  1190.  *
  1191.  *--------------------------------------------------------------
  1192.  */
  1193.  
  1194. static int
  1195. TextMarkCmd(textPtr, interp, argc, argv)
  1196.     register TkText *textPtr;    /* Information about text widget. */
  1197.     Tcl_Interp *interp;        /* Current interpreter. */
  1198.     int argc;            /* Number of arguments. */
  1199.     char **argv;        /* Argument strings.  Someone else has already
  1200.                  * parsed this command enough to know that
  1201.                  * argv[1] is "mark". */
  1202. {
  1203.     int length, line, ch, i;
  1204.     char c;
  1205.     Tcl_HashEntry *hPtr;
  1206.     TkAnnotation *markPtr;
  1207.     Tcl_HashSearch search;
  1208.  
  1209.     if (argc < 3) {
  1210.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  1211.         argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
  1212.     return TCL_ERROR;
  1213.     }
  1214.     c = argv[2][0];
  1215.     length = strlen(argv[2]);
  1216.     if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
  1217.     if (argc != 3) {
  1218.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1219.             argv[0], " mark names\"", (char *) NULL);
  1220.         return TCL_ERROR;
  1221.     }
  1222.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  1223.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  1224.         Tcl_AppendElement(interp,
  1225.             Tcl_GetHashKey(&textPtr->markTable, hPtr));
  1226.     }
  1227.     } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
  1228.     if (argc != 5) {
  1229.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1230.             argv[0], " mark set markName index\"", (char *) NULL);
  1231.         return TCL_ERROR;
  1232.     }
  1233.     if (TkTextGetIndex(interp, textPtr, argv[4], &line, &ch) != TCL_OK) {
  1234.         return TCL_ERROR;
  1235.     }
  1236.     TkTextSetMark(textPtr, argv[3], line, ch);
  1237.     } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
  1238.     if (argc < 4) {
  1239.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1240.             argv[0], " mark unset markName ?markName ...?\"",
  1241.             (char *) NULL);
  1242.         return TCL_ERROR;
  1243.     }
  1244.     for (i = 3; i < argc; i++) {
  1245.         hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
  1246.         if (hPtr != NULL) {
  1247.         markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
  1248.         if (markPtr == textPtr->insertAnnotPtr) {
  1249.             interp->result = "can't delete \"insert\" mark";
  1250.             return TCL_ERROR;
  1251.         }
  1252.         if (markPtr == textPtr->currentAnnotPtr) {
  1253.             interp->result = "can't delete \"current\" mark";
  1254.             return TCL_ERROR;
  1255.         }
  1256.         TkBTreeRemoveAnnotation(markPtr);
  1257.         Tcl_DeleteHashEntry(hPtr);
  1258.         ckfree((char *) markPtr);
  1259.         }
  1260.     }
  1261.     } else {
  1262.     Tcl_AppendResult(interp, "bad mark option \"", argv[2],
  1263.         "\":  must be names, set, or unset",
  1264.         (char *) NULL);
  1265.     return TCL_ERROR;
  1266.     }
  1267.     return TCL_OK;
  1268. }
  1269.  
  1270. /*
  1271.  *----------------------------------------------------------------------
  1272.  *
  1273.  * TkTextSetMark --
  1274.  *
  1275.  *    Set a mark to a particular position, creating a new mark if
  1276.  *    one doesn't already exist.
  1277.  *
  1278.  * Results:
  1279.  *    The return value is a pointer to the mark that was just set.
  1280.  *
  1281.  * Side effects:
  1282.  *    A new mark is created, or an existing mark is moved.
  1283.  *
  1284.  *----------------------------------------------------------------------
  1285.  */
  1286.  
  1287. TkAnnotation *
  1288. TkTextSetMark(textPtr, name, line, ch)
  1289.     TkText *textPtr;        /* Text widget in which to create mark. */
  1290.     char *name;            /* Name of mark to set. */
  1291.     int line;            /* Index of line at which to place mark. */
  1292.     int ch;            /* Index of character within line at which
  1293.                  * to place mark. */
  1294. {
  1295.     Tcl_HashEntry *hPtr;
  1296.     TkAnnotation *markPtr;
  1297.     int new;
  1298.  
  1299.     hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
  1300.     markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
  1301.     if (!new) {
  1302.     /*
  1303.      * If this is the insertion point that's being moved, be sure
  1304.      * to force a display update at the old position.
  1305.      */
  1306.  
  1307.     if (markPtr == textPtr->insertAnnotPtr) {
  1308.         int oldLine;
  1309.  
  1310.         oldLine = TkBTreeLineIndex(markPtr->linePtr);
  1311.         TkTextLinesChanged(textPtr, oldLine, oldLine);
  1312.     }
  1313.     TkBTreeRemoveAnnotation(markPtr);
  1314.     } else {
  1315.     markPtr = (TkAnnotation *) ckalloc(sizeof(TkAnnotation));
  1316.     markPtr->type = TK_ANNOT_MARK;
  1317.     markPtr->info.hPtr = hPtr;
  1318.     Tcl_SetHashValue(hPtr, markPtr);
  1319.     }
  1320.     if (line < 0) {
  1321.     line = 0;
  1322.     markPtr->ch = 0;
  1323.     } else if (ch < 0) {
  1324.     markPtr->ch = 0;
  1325.     } else {
  1326.     markPtr->ch = ch;
  1327.     }
  1328.     markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
  1329.     if (markPtr->linePtr == NULL) {
  1330.     line = TkBTreeNumLines(textPtr->tree)-1;
  1331.     markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
  1332.     markPtr->ch = markPtr->linePtr->numBytes-1;
  1333.     } else {
  1334.     if (markPtr->ch >= markPtr->linePtr->numBytes) {
  1335.         TkTextLine *nextLinePtr;
  1336.  
  1337.         nextLinePtr = TkBTreeNextLine(markPtr->linePtr);
  1338.         if (nextLinePtr == NULL) {
  1339.         markPtr->ch = markPtr->linePtr->numBytes-1;
  1340.         } else {
  1341.         markPtr->linePtr = nextLinePtr;
  1342.         line++;
  1343.         markPtr->ch = 0;
  1344.         }
  1345.     }
  1346.     }
  1347.     TkBTreeAddAnnotation(markPtr);
  1348.  
  1349.     /*
  1350.      * If the mark is the insertion cursor, then update the screen at the
  1351.      * mark's new location.
  1352.      */
  1353.  
  1354.     if (markPtr == textPtr->insertAnnotPtr) {
  1355.     TkTextLinesChanged(textPtr, line, line);
  1356.     }
  1357.     return markPtr;
  1358. }
  1359.  
  1360. /*
  1361.  *----------------------------------------------------------------------
  1362.  *
  1363.  * TextBlinkProc --
  1364.  *
  1365.  *    This procedure is called as a timer handler to blink the
  1366.  *    insertion cursor off and on.
  1367.  *
  1368.  * Results:
  1369.  *    None.
  1370.  *
  1371.  * Side effects:
  1372.  *    The cursor gets turned on or off, redisplay gets invoked,
  1373.  *    and this procedure reschedules itself.
  1374.  *
  1375.  *----------------------------------------------------------------------
  1376.  */
  1377.  
  1378. static void
  1379. TextBlinkProc(clientData)
  1380.     ClientData clientData;    /* Pointer to record describing text. */
  1381. {
  1382.     register TkText *textPtr = (TkText *) clientData;
  1383.     int lineNum;
  1384.  
  1385.     if (!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
  1386.     return;
  1387.     }
  1388.     if (textPtr->flags & INSERT_ON) {
  1389.     textPtr->flags &= ~INSERT_ON;
  1390.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1391.         textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
  1392.     } else {
  1393.     textPtr->flags |= INSERT_ON;
  1394.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1395.         textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
  1396.     }
  1397.     lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
  1398.     TkTextLinesChanged(textPtr, lineNum, lineNum);
  1399. }
  1400.  
  1401. /*
  1402.  *--------------------------------------------------------------
  1403.  *
  1404.  * TextScanCmd --
  1405.  *
  1406.  *    This procedure is invoked to process the "scan" options of
  1407.  *    the widget command for text widgets. See the user documentation
  1408.  *    for details on what it does.
  1409.  *
  1410.  * Results:
  1411.  *    A standard Tcl result.
  1412.  *
  1413.  * Side effects:
  1414.  *    See the user documentation.
  1415.  *
  1416.  *--------------------------------------------------------------
  1417.  */
  1418.  
  1419. static int
  1420. TextScanCmd(textPtr, interp, argc, argv)
  1421.     register TkText *textPtr;    /* Information about text widget. */
  1422.     Tcl_Interp *interp;        /* Current interpreter. */
  1423.     int argc;            /* Number of arguments. */
  1424.     char **argv;        /* Argument strings.  Someone else has already
  1425.                  * parsed this command enough to know that
  1426.                  * argv[1] is "tag". */
  1427. {
  1428.     int length, y, line, lastLine;
  1429.     char c;
  1430.  
  1431.     if (argc != 4) {
  1432.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  1433.         argv[0], " scan mark|dragto y\"", (char *) NULL);
  1434.     return TCL_ERROR;
  1435.     }
  1436.     if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
  1437.     return TCL_ERROR;
  1438.     }
  1439.     c = argv[2][0];
  1440.     length = strlen(argv[2]);
  1441.     if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
  1442.     /*
  1443.      * Amplify the difference between the current y position and the
  1444.      * mark position to compute how many lines up or down the view
  1445.      * should shift, then update the mark position to correspond to
  1446.      * the new view.  If we run off the top or bottom of the text,
  1447.      * reset the mark point so that the current position continues
  1448.      * to correspond to the edge of the window.  This means that the
  1449.      * picture will start dragging as soon as the mouse reverses
  1450.      * direction (without this reset, might have to slide mouse a
  1451.      * long ways back before the picture starts moving again).
  1452.      */
  1453.  
  1454.     line = textPtr->scanMarkLine + (10*(textPtr->scanMarkY - y))
  1455.         / (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
  1456.     lastLine = TkBTreeNumLines(textPtr->tree) - 1;
  1457.     if (line < 0) {
  1458.         textPtr->scanMarkLine = line = 0;
  1459.         textPtr->scanMarkY = y;
  1460.     } else if (line > lastLine) {
  1461.         textPtr->scanMarkLine = line = lastLine;
  1462.         textPtr->scanMarkY = y;
  1463.     }
  1464.     TkTextSetView(textPtr, line, 0);
  1465.     } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
  1466.     textPtr->scanMarkLine = TkBTreeLineIndex(textPtr->topLinePtr);
  1467.     textPtr->scanMarkY = y;
  1468.     } else {
  1469.     Tcl_AppendResult(interp, "bad scan option \"", argv[2],
  1470.         "\":  must be mark or dragto", (char *) NULL);
  1471.     return TCL_ERROR;
  1472.     }
  1473.     return TCL_OK;
  1474. }
  1475.