tkText.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:91k
源码类别:

通讯编程

开发平台:

Visual C++

  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-1994 The Regents of the University of California.
  11.  * Copyright (c) 1994-1996 Sun Microsystems, Inc.
  12.  * Copyright (c) 1999 by Scriptics Corporation.
  13.  *
  14.  * See the file "license.terms" for information on usage and redistribution
  15.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  16.  *
  17.  * RCS: @(#) $Id: tkText.c,v 1.33.2.7 2007/12/13 00:31:33 hobbs Exp $
  18.  */
  19. #include "default.h"
  20. #include "tkPort.h"
  21. #include "tkInt.h"
  22. #include "tkUndo.h"
  23. #if defined(MAC_TCL) || defined(MAC_OSX_TK)
  24. #define Style TkStyle
  25. #define DInfo TkDInfo
  26. #endif
  27. #include "tkText.h"
  28. /*
  29.  * Custom options for handling "-state"
  30.  */
  31. static Tk_CustomOption stateOption = {
  32.     (Tk_OptionParseProc *) TkStateParseProc,
  33.     TkStatePrintProc, (ClientData) NULL /* only "normal" and "disabled" */
  34. };
  35. /*
  36.  * Information used to parse text configuration options:
  37.  */
  38. static Tk_ConfigSpec configSpecs[] = {
  39.     {TK_CONFIG_BOOLEAN, "-autoseparators", "autoSeparators",
  40.         "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS,
  41.         Tk_Offset(TkText, autoSeparators), 0},
  42.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  43. DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY},
  44.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  45. DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY},
  46.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  47. (char *) NULL, 0, 0},
  48.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  49. (char *) NULL, 0, 0},
  50.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  51. DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0},
  52.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  53. DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK},
  54.     {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
  55. "ExportSelection", DEF_TEXT_EXPORT_SELECTION,
  56. Tk_Offset(TkText, exportSelection), 0},
  57.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  58. (char *) NULL, 0, 0},
  59.     {TK_CONFIG_FONT, "-font", "font", "Font",
  60. DEF_TEXT_FONT, Tk_Offset(TkText, tkfont), 0},
  61.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  62. DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0},
  63.     {TK_CONFIG_PIXELS, "-height", "height", "Height",
  64. DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0},
  65.     {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
  66. "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG,
  67. Tk_Offset(TkText, highlightBgColorPtr), 0},
  68.     {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
  69. DEF_TEXT_HIGHLIGHT, Tk_Offset(TkText, highlightColorPtr), 0},
  70.     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
  71. "HighlightThickness",
  72. DEF_TEXT_HIGHLIGHT_WIDTH, Tk_Offset(TkText, highlightWidth), 0},
  73.     {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
  74. DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0},
  75.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  76. DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth),
  77. TK_CONFIG_COLOR_ONLY},
  78.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  79. DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth),
  80. TK_CONFIG_MONO_ONLY},
  81.     {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
  82. DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0},
  83.     {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
  84. DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0},
  85.     {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
  86. DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0},
  87.     {TK_CONFIG_INT, "-maxundo", "maxUndo", "MaxUndo",
  88. DEF_TEXT_MAX_UNDO, Tk_Offset(TkText, maxUndo), 0},
  89.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  90. DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0},
  91.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  92. DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0},
  93.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  94. DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0},
  95.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  96. DEF_TEXT_SELECT_COLOR, Tk_Offset(TkText, selBorder),
  97. TK_CONFIG_COLOR_ONLY},
  98.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  99. DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder),
  100. TK_CONFIG_MONO_ONLY},
  101.     {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  102. DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBdString),
  103. TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  104.     {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  105. DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBdString),
  106. TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  107.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  108. DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr),
  109. TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  110.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  111. DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr),
  112. TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  113.     {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
  114. DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0},
  115.     {TK_CONFIG_PIXELS, "-spacing1", "spacing1", "Spacing",
  116. DEF_TEXT_SPACING1, Tk_Offset(TkText, spacing1),
  117. TK_CONFIG_DONT_SET_DEFAULT},
  118.     {TK_CONFIG_PIXELS, "-spacing2", "spacing2", "Spacing",
  119. DEF_TEXT_SPACING2, Tk_Offset(TkText, spacing2),
  120. TK_CONFIG_DONT_SET_DEFAULT},
  121.     {TK_CONFIG_PIXELS, "-spacing3", "spacing3", "Spacing",
  122. DEF_TEXT_SPACING3, Tk_Offset(TkText, spacing3),
  123. TK_CONFIG_DONT_SET_DEFAULT},
  124.     {TK_CONFIG_CUSTOM, "-state", "state", "State",
  125. DEF_TEXT_STATE, Tk_Offset(TkText, state), 0, &stateOption},
  126.     {TK_CONFIG_STRING, "-tabs", "tabs", "Tabs",
  127. DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionString), TK_CONFIG_NULL_OK},
  128.     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
  129. DEF_TEXT_TAKE_FOCUS, Tk_Offset(TkText, takeFocus),
  130. TK_CONFIG_NULL_OK},
  131.     {TK_CONFIG_BOOLEAN, "-undo", "undo", "Undo",
  132.         DEF_TEXT_UNDO, Tk_Offset(TkText, undo), 0},
  133.     {TK_CONFIG_INT, "-width", "width", "Width",
  134. DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0},
  135.     {TK_CONFIG_CUSTOM, "-wrap", "wrap", "Wrap",
  136. DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0, &TkTextWrapModeOption},
  137.     {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
  138. DEF_TEXT_XSCROLL_COMMAND, Tk_Offset(TkText, xScrollCmd),
  139. TK_CONFIG_NULL_OK},
  140.     {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
  141. DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd),
  142. TK_CONFIG_NULL_OK},
  143.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  144. (char *) NULL, 0, 0}
  145. };
  146. /*
  147.  * Boolean variable indicating whether or not special debugging code
  148.  * should be executed.
  149.  */
  150. int tkTextDebug = 0;
  151. /*
  152.  * Custom options for handling "-wrap":
  153.  */
  154. static int WrapModeParseProc _ANSI_ARGS_((ClientData clientData,
  155.     Tcl_Interp *interp, Tk_Window tkwin,
  156.     CONST char *value, char *widgRec, int offset));
  157. static char * WrapModePrintProc _ANSI_ARGS_((ClientData clientData,
  158.     Tk_Window tkwin, char *widgRec, int offset,
  159.     Tcl_FreeProc **freeProcPtr));
  160. Tk_CustomOption TkTextWrapModeOption = {
  161.     WrapModeParseProc,
  162.     WrapModePrintProc,
  163.     (ClientData) NULL
  164. };
  165. /*
  166.  *--------------------------------------------------------------
  167.  *
  168.  * WrapModeParseProc --
  169.  *
  170.  * This procedure is invoked during option processing to handle
  171.  * "-wrap" options for text widgets.
  172.  *
  173.  * Results:
  174.  * A standard Tcl return value.
  175.  *
  176.  * Side effects:
  177.  * The wrap mode for a given item gets replaced by the wrap mode
  178.  * indicated in the value argument.
  179.  *
  180.  *--------------------------------------------------------------
  181.  */
  182. static int
  183. WrapModeParseProc(clientData, interp, tkwin, value, widgRec, offset)
  184.     ClientData clientData; /* some flags.*/
  185.     Tcl_Interp *interp; /* Used for reporting errors. */
  186.     Tk_Window tkwin; /* Window containing canvas widget. */
  187.     CONST char *value; /* Value of option (list of tag
  188.  * names). */
  189.     char *widgRec; /* Pointer to record for item. */
  190.     int offset; /* Offset into item. */
  191. {
  192.     int c;
  193.     size_t length;
  194.     register TkWrapMode *wrapPtr = (TkWrapMode *) (widgRec + offset);
  195.     if(value == NULL || *value == 0) {
  196. *wrapPtr = TEXT_WRAPMODE_NULL;
  197. return TCL_OK;
  198.     }
  199.     c = value[0];
  200.     length = strlen(value);
  201.     if ((c == 'c') && (strncmp(value, "char", length) == 0)) {
  202. *wrapPtr = TEXT_WRAPMODE_CHAR;
  203. return TCL_OK;
  204.     }
  205.     if ((c == 'n') && (strncmp(value, "none", length) == 0)) {
  206. *wrapPtr = TEXT_WRAPMODE_NONE;
  207. return TCL_OK;
  208.     }
  209.     if ((c == 'w') && (strncmp(value, "word", length) == 0)) {
  210. *wrapPtr = TEXT_WRAPMODE_WORD;
  211. return TCL_OK;
  212.     }
  213.     Tcl_AppendResult(interp, "bad wrap mode "", value,
  214.     "": must be char, none, or word",
  215.     (char *) NULL);
  216.     *wrapPtr = TEXT_WRAPMODE_CHAR;
  217.     return TCL_ERROR;
  218. }
  219. /*
  220.  *--------------------------------------------------------------
  221.  *
  222.  * WrapModePrintProc --
  223.  *
  224.  * This procedure is invoked by the Tk configuration code
  225.  * to produce a printable string for the "-wrap" configuration
  226.  * option for canvas items.
  227.  *
  228.  * Results:
  229.  * The return value is a string describing the state for
  230.  * the item referred to by "widgRec".  In addition, *freeProcPtr
  231.  * is filled in with the address of a procedure to call to free
  232.  * the result string when it's no longer needed (or NULL to
  233.  * indicate that the string doesn't need to be freed).
  234.  *
  235.  * Side effects:
  236.  * None.
  237.  *
  238.  *--------------------------------------------------------------
  239.  */
  240. static char *
  241. WrapModePrintProc(clientData, tkwin, widgRec, offset, freeProcPtr)
  242.     ClientData clientData; /* Ignored. */
  243.     Tk_Window tkwin; /* Window containing canvas widget. */
  244.     char *widgRec; /* Pointer to record for item. */
  245.     int offset; /* Ignored. */
  246.     Tcl_FreeProc **freeProcPtr; /* Pointer to variable to fill in with
  247.  * information about how to reclaim
  248.  * storage for return string. */
  249. {
  250.     register TkWrapMode *wrapPtr = (TkWrapMode *) (widgRec + offset);
  251.     if (*wrapPtr==TEXT_WRAPMODE_CHAR) {
  252. return "char";
  253.     } else if (*wrapPtr==TEXT_WRAPMODE_NONE) {
  254. return "none";
  255.     } else if (*wrapPtr==TEXT_WRAPMODE_WORD) {
  256. return "word";
  257.     } else {
  258. return "";
  259.     }
  260. }
  261. /*
  262.  * Forward declarations for procedures defined later in this file:
  263.  */
  264. static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
  265.     TkText *textPtr, int argc, CONST char **argv,
  266.     int flags));
  267. static int DeleteChars _ANSI_ARGS_((TkText *textPtr,
  268.     CONST char *index1String, CONST char *index2String,
  269.     TkTextIndex *indexPtr1, TkTextIndex *indexPtr2));
  270. static void DestroyText _ANSI_ARGS_((char *memPtr));
  271. static void InsertChars _ANSI_ARGS_((TkText *textPtr,
  272.     TkTextIndex *indexPtr, CONST char *string));
  273. static void TextBlinkProc _ANSI_ARGS_((ClientData clientData));
  274. static void TextCmdDeletedProc _ANSI_ARGS_((
  275.     ClientData clientData));
  276. static void TextEventProc _ANSI_ARGS_((ClientData clientData,
  277.     XEvent *eventPtr));
  278. static int TextFetchSelection _ANSI_ARGS_((ClientData clientData,
  279.     int offset, char *buffer, int maxBytes));
  280. static int TextIndexSortProc _ANSI_ARGS_((CONST VOID *first,
  281.     CONST VOID *second));
  282. static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr,
  283.     Tcl_Interp *interp, int argc, CONST char **argv));
  284. static int TextEditCmd _ANSI_ARGS_((TkText *textPtr,
  285.     Tcl_Interp *interp, int argc, CONST char **argv));
  286. static int TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
  287.     Tcl_Interp *interp, int argc, CONST char **argv));
  288. static void TextWorldChanged _ANSI_ARGS_((
  289.     ClientData instanceData));
  290. static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr,
  291.     Tcl_Interp *interp, int argc, CONST char **argv));
  292. static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp, 
  293.     TkText *textPtr, int what, TkTextLine *linePtr,
  294.     int start, int end, int lineno,
  295.     CONST char *command));
  296. static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key,
  297.     char *value, CONST char * command,
  298.     TkTextIndex *index, int what));
  299. static int TextEditUndo _ANSI_ARGS_((TkText *textPtr));
  300. static int TextEditRedo _ANSI_ARGS_((TkText *textPtr));
  301. static void TextGetText _ANSI_ARGS_((TkTextIndex * index1,
  302.     TkTextIndex * index2, Tcl_DString *dsPtr));
  303. static void GenerateModifiedEvent(TkText *textPtr);
  304. static void updateDirtyFlag _ANSI_ARGS_((TkText *textPtr));
  305. /*
  306.  * The structure below defines text class behavior by means of procedures
  307.  * that can be invoked from generic window code.
  308.  */
  309. static Tk_ClassProcs textClass = {
  310.     sizeof(Tk_ClassProcs), /* size */
  311.     TextWorldChanged, /* worldChangedProc */
  312. };
  313. /*
  314.  *--------------------------------------------------------------
  315.  *
  316.  * Tk_TextCmd --
  317.  *
  318.  * This procedure is invoked to process the "text" Tcl command.
  319.  * See the user documentation for details on what it does.
  320.  *
  321.  * Results:
  322.  * A standard Tcl result.
  323.  *
  324.  * Side effects:
  325.  * See the user documentation.
  326.  *
  327.  *--------------------------------------------------------------
  328.  */
  329. int
  330. Tk_TextCmd(clientData, interp, argc, argv)
  331.     ClientData clientData; /* Main window associated with
  332.  * interpreter. */
  333.     Tcl_Interp *interp; /* Current interpreter. */
  334.     int argc; /* Number of arguments. */
  335.     CONST char **argv; /* Argument strings. */
  336. {
  337.     Tk_Window tkwin = (Tk_Window) clientData;
  338.     Tk_Window new;
  339.     register TkText *textPtr;
  340.     TkTextIndex startIndex;
  341.     if (argc < 2) {
  342. Tcl_AppendResult(interp, "wrong # args: should be "",
  343. argv[0], " pathName ?options?"", (char *) NULL);
  344. return TCL_ERROR;
  345.     }
  346.     /*
  347.      * Create the window.
  348.      */
  349.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  350.     if (new == NULL) {
  351. return TCL_ERROR;
  352.     }
  353.     /*
  354.      * Create the text widget and initialize everything to zero,
  355.      * then set the necessary initial (non-NULL) values.
  356.      */
  357.     textPtr = (TkText *) ckalloc(sizeof(TkText));
  358.     memset((VOID *) textPtr, 0, sizeof(TkText));
  359.     textPtr->tkwin = new;
  360.     textPtr->display = Tk_Display(new);
  361.     textPtr->interp = interp;
  362.     textPtr->widgetCmd = Tcl_CreateCommand(interp,
  363.     Tk_PathName(textPtr->tkwin), TextWidgetCmd,
  364.     (ClientData) textPtr, TextCmdDeletedProc);
  365.     textPtr->tree = TkBTreeCreate(textPtr);
  366.     Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
  367.     Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
  368.     Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS);
  369.     Tcl_InitHashTable(&textPtr->imageTable, TCL_STRING_KEYS);
  370.     textPtr->state = TK_STATE_NORMAL;
  371.     textPtr->relief = TK_RELIEF_FLAT;
  372.     textPtr->cursor = None;
  373.     textPtr->charWidth = 1;
  374.     textPtr->wrapMode = TEXT_WRAPMODE_CHAR;
  375.     textPtr->prevWidth = Tk_Width(new);
  376.     textPtr->prevHeight = Tk_Height(new);
  377.     TkTextCreateDInfo(textPtr);
  378.     TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex);
  379.     TkTextSetYView(textPtr, &startIndex, 0);
  380.     textPtr->exportSelection = 1;
  381.     textPtr->pickEvent.type = LeaveNotify;
  382.     textPtr->undoStack = TkUndoInitStack(interp,0);
  383.     textPtr->undo = 1;
  384.     textPtr->isDirtyIncrement = 1;
  385.     textPtr->autoSeparators = 1;
  386.     textPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
  387.     /*
  388.      * Create the "sel" tag and the "current" and "insert" marks.
  389.      */
  390.     textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel");
  391.     textPtr->selTagPtr->reliefString =
  392.     (char *) ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF));
  393.     strcpy(textPtr->selTagPtr->reliefString, DEF_TEXT_SELECT_RELIEF);
  394.     Tk_GetRelief(interp, DEF_TEXT_SELECT_RELIEF, &(textPtr->selTagPtr->relief));
  395.     textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
  396.     textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &startIndex);
  397.     Tk_SetClass(textPtr->tkwin, "Text");
  398.     Tk_SetClassProcs(textPtr->tkwin, &textClass, (ClientData) textPtr);
  399.     Tk_CreateEventHandler(textPtr->tkwin,
  400.     ExposureMask|StructureNotifyMask|FocusChangeMask,
  401.     TextEventProc, (ClientData) textPtr);
  402.     Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
  403.     |ButtonPressMask|ButtonReleaseMask|EnterWindowMask
  404.     |LeaveWindowMask|PointerMotionMask|VirtualEventMask,
  405.     TkTextBindProc, (ClientData) textPtr);
  406.     Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING,
  407.     TextFetchSelection, (ClientData) textPtr, XA_STRING);
  408.     if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
  409. Tk_DestroyWindow(textPtr->tkwin);
  410. return TCL_ERROR;
  411.     }
  412.     Tcl_SetResult(interp, Tk_PathName(textPtr->tkwin), TCL_STATIC);
  413.     return TCL_OK;
  414. }
  415. /*
  416.  *--------------------------------------------------------------
  417.  *
  418.  * TextWidgetCmd --
  419.  *
  420.  * This procedure is invoked to process the Tcl command
  421.  * that corresponds to a text widget.  See the user
  422.  * documentation for details on what it does.
  423.  *
  424.  * Results:
  425.  * A standard Tcl result.
  426.  *
  427.  * Side effects:
  428.  * See the user documentation.
  429.  *
  430.  *--------------------------------------------------------------
  431.  */
  432. static int
  433. TextWidgetCmd(clientData, interp, argc, argv)
  434.     ClientData clientData; /* Information about text widget. */
  435.     Tcl_Interp *interp; /* Current interpreter. */
  436.     int argc; /* Number of arguments. */
  437.     CONST char **argv; /* Argument strings. */
  438. {
  439.     register TkText *textPtr = (TkText *) clientData;
  440.     int c, result = TCL_OK;
  441.     size_t length;
  442.     TkTextIndex index1, index2;
  443.     if (argc < 2) {
  444. Tcl_AppendResult(interp, "wrong # args: should be "",
  445. argv[0], " option ?arg arg ...?"", (char *) NULL);
  446. return TCL_ERROR;
  447.     }
  448.     Tcl_Preserve((ClientData) textPtr);
  449.     c = argv[1][0];
  450.     length = strlen(argv[1]);
  451.     if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
  452. int x, y, width, height;
  453. if (argc != 3) {
  454.     Tcl_AppendResult(interp, "wrong # args: should be "",
  455.     argv[0], " bbox index"", (char *) NULL);
  456.     result = TCL_ERROR;
  457.     goto done;
  458. }
  459. if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  460.     result = TCL_ERROR;
  461.     goto done;
  462. }
  463. if (TkTextCharBbox(textPtr, &index1, &x, &y, &width, &height) == 0) {
  464.     char buf[TCL_INTEGER_SPACE * 4];
  465.     
  466.     sprintf(buf, "%d %d %d %d", x, y, width, height);
  467.     Tcl_SetResult(interp, buf, TCL_VOLATILE);
  468. }
  469.     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  470.     && (length >= 2)) {
  471. if (argc != 3) {
  472.     Tcl_AppendResult(interp, "wrong # args: should be "",
  473.     argv[0], " cget option"",
  474.     (char *) NULL);
  475.     result = TCL_ERROR;
  476.     goto done;
  477. }
  478. result = Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs,
  479. (char *) textPtr, argv[2], 0);
  480.     } else if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
  481.     && (length >= 3)) {
  482. int relation, value;
  483. CONST char *p;
  484. if (argc != 5) {
  485.     Tcl_AppendResult(interp, "wrong # args: should be "",
  486.     argv[0], " compare index1 op index2"", (char *) NULL);
  487.     result = TCL_ERROR;
  488.     goto done;
  489. }
  490. if ((TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK)
  491. || (TkTextGetIndex(interp, textPtr, argv[4], &index2)
  492. != TCL_OK)) {
  493.     result = TCL_ERROR;
  494.     goto done;
  495. }
  496. relation = TkTextIndexCmp(&index1, &index2);
  497. p = argv[3];
  498. if (p[0] == '<') {
  499. value = (relation < 0);
  500.     if ((p[1] == '=') && (p[2] == 0)) {
  501. value = (relation <= 0);
  502.     } else if (p[1] != 0) {
  503. compareError:
  504. Tcl_AppendResult(interp, "bad comparison operator "",
  505. argv[3], "": must be <, <=, ==, >=, >, or !=",
  506. (char *) NULL);
  507. result = TCL_ERROR;
  508. goto done;
  509.     }
  510. } else if (p[0] == '>') {
  511. value = (relation > 0);
  512.     if ((p[1] == '=') && (p[2] == 0)) {
  513. value = (relation >= 0);
  514.     } else if (p[1] != 0) {
  515. goto compareError;
  516.     }
  517. } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
  518.     value = (relation == 0);
  519. } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
  520.     value = (relation != 0);
  521. } else {
  522.     goto compareError;
  523. }
  524. Tcl_SetResult(interp, ((value) ? "1" : "0"), TCL_STATIC);
  525.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  526.     && (length >= 3)) {
  527. if (argc == 2) {
  528.     result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  529.     (char *) textPtr, (char *) NULL, 0);
  530. } else if (argc == 3) {
  531.     result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  532.     (char *) textPtr, argv[2], 0);
  533. } else {
  534.     result = ConfigureText(interp, textPtr, argc-2, argv+2,
  535.     TK_CONFIG_ARGV_ONLY);
  536. }
  537.     } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
  538.     && (length >= 3)) {
  539. if (argc > 3) {
  540.     Tcl_AppendResult(interp, "wrong # args: should be "",
  541.     argv[0], " debug boolean"", (char *) NULL);
  542.     result = TCL_ERROR;
  543.     goto done;
  544. }
  545. if (argc == 2) {
  546.     Tcl_SetResult(interp, ((tkBTreeDebug) ? "1" : "0"), TCL_STATIC);
  547. } else {
  548.     if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
  549. result = TCL_ERROR;
  550. goto done;
  551.     }
  552.     tkTextDebug = tkBTreeDebug;
  553. }
  554.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  555.     && (length >= 3)) {
  556. int i;
  557. if (argc < 3) {
  558.     Tcl_AppendResult(interp, "wrong # args: should be "",
  559.     argv[0], " delete index1 ?index2 ...?"", (char *) NULL);
  560.     result = TCL_ERROR;
  561.     goto done;
  562. }
  563. if (textPtr->state == TK_STATE_NORMAL) {
  564.     if (argc < 5) {
  565. /*
  566.  * Simple case requires no predetermination of indices.
  567.  */
  568. result = DeleteChars(textPtr, argv[2],
  569. (argc == 4) ? argv[3] : NULL, NULL, NULL);
  570.     } else {
  571. /*
  572.  * Multi-index pair case requires that we prevalidate the
  573.  * indices and sort from last to first so that deletes
  574.  * occur in the exact (unshifted) text.  It also needs to
  575.  * handle partial and fully overlapping ranges.  We have to
  576.  * do this with multiple passes.
  577.  */
  578. TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
  579. char *useIdx;
  580. argc -= 2;
  581. argv += 2;
  582. indices = (TkTextIndex *)
  583.     ckalloc((argc + 1) * sizeof(TkTextIndex));
  584. /*
  585.  * First pass verifies that all indices are valid.
  586.  */
  587. for (i = 0; i < argc; i++) {
  588.     if (TkTextGetIndex(interp, textPtr, argv[i],
  589.     &indices[i]) != TCL_OK) {
  590. result = TCL_ERROR;
  591. ckfree((char *) indices);
  592. goto done;
  593.     }
  594. }
  595. /*
  596.  * Pad out the pairs evenly to make later code easier.
  597.  */
  598. if (argc & 1) {
  599.     indices[i] = indices[i-1];
  600.     TkTextIndexForwChars(&indices[i], 1, &indices[i]);
  601.     argc++;
  602. }
  603. useIdx = (char *) ckalloc((unsigned) argc);
  604. memset(useIdx, 0, (unsigned) argc);
  605. /*
  606.  * Do a decreasing order sort so that we delete the end
  607.  * ranges first to maintain index consistency.
  608.  */
  609. qsort((VOID *) indices, (unsigned) (argc / 2),
  610. 2 * sizeof(TkTextIndex), TextIndexSortProc);
  611. lastStart = NULL;
  612. /*
  613.  * Second pass will handle bogus ranges (end < start) and
  614.  * overlapping ranges.
  615.  */
  616. for (i = 0; i < argc; i += 2) {
  617.     ixStart = &indices[i];
  618.     ixEnd   = &indices[i+1];
  619.     if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
  620. continue;
  621.     }
  622.     if (lastStart) {
  623. if (TkTextIndexCmp(ixStart, lastStart) == 0) {
  624.     /*
  625.      * Start indices were equal, and the sort placed
  626.      * the longest range first, so skip this one.
  627.      */
  628.     continue;
  629. } else if (TkTextIndexCmp(lastStart, ixEnd) < 0) {
  630.     /*
  631.      * The next pair has a start range before the end
  632.      * point of the last range.  Constrain the delete
  633.      * range, but use the pointer values.
  634.      */
  635.     *ixEnd = *lastStart;
  636.     if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
  637. continue;
  638.     }
  639. }
  640.     }
  641.     lastStart = ixStart;
  642.     useIdx[i]   = 1;
  643. }
  644. /*
  645.  * Final pass take the input from the previous and deletes
  646.  * the ranges which are flagged to be deleted.
  647.  */
  648. for (i = 0; i < argc; i += 2) {
  649.     if (useIdx[i]) {
  650. /*
  651.  * We don't need to check the return value because all
  652.  * indices are preparsed above.
  653.  */
  654. DeleteChars(textPtr, NULL, NULL,
  655. &indices[i], &indices[i+1]);
  656.     }
  657. }
  658. ckfree((char *) indices);
  659.     }
  660. }
  661.     } else if ((c == 'd') && (strncmp(argv[1], "dlineinfo", length) == 0)
  662.     && (length >= 2)) {
  663. int x, y, width, height, base;
  664. if (argc != 3) {
  665.     Tcl_AppendResult(interp, "wrong # args: should be "",
  666.     argv[0], " dlineinfo index"", (char *) NULL);
  667.     result = TCL_ERROR;
  668.     goto done;
  669. }
  670. if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  671.     result = TCL_ERROR;
  672.     goto done;
  673. }
  674. if (TkTextDLineInfo(textPtr, &index1, &x, &y, &width, &height, &base)
  675. == 0) {
  676.     char buf[TCL_INTEGER_SPACE * 5];
  677.     
  678.     sprintf(buf, "%d %d %d %d %d", x, y, width, height, base);
  679.     Tcl_SetResult(interp, buf, TCL_VOLATILE);
  680. }
  681.     } else if ((c == 'e') && (strncmp(argv[1], "edit", length) == 0)) {
  682.         result = TextEditCmd(textPtr, interp, argc, argv);
  683.     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
  684. Tcl_Obj *objPtr = NULL;
  685. Tcl_DString ds;
  686. int i, found = 0;
  687. if (argc < 3) {
  688.     Tcl_AppendResult(interp, "wrong # args: should be "",
  689.     argv[0], " get index1 ?index2 ...?"", (char *) NULL);
  690.     result = TCL_ERROR;
  691.     goto done;
  692. }
  693. for (i = 2; i < argc; i += 2) {
  694.     if (TkTextGetIndex(interp, textPtr, argv[i], &index1) != TCL_OK) {
  695. result = TCL_ERROR;
  696. goto done;
  697.     }
  698.     if (i+1 == argc) {
  699. index2 = index1;
  700. TkTextIndexForwChars(&index2, 1, &index2);
  701.     } else if (TkTextGetIndex(interp, textPtr, argv[i+1], &index2)
  702.     != TCL_OK) {
  703. if (objPtr) {
  704.     Tcl_DecrRefCount(objPtr);
  705. }
  706. result = TCL_ERROR;
  707. goto done;
  708.     }
  709.     if (TkTextIndexCmp(&index1, &index2) < 0) {
  710. /* 
  711.  * Place the text in a DString and move it to the result.
  712.  * Since this could in principle be a megabyte or more, we
  713.  * want to do it efficiently!
  714.  */
  715. TextGetText(&index1, &index2, &ds);
  716. found++;
  717. if (found == 1) {
  718.     Tcl_DStringResult(interp, &ds);
  719. } else {
  720.     if (found == 2) {
  721. /*
  722.  * Move the first item we put into the result into
  723.  * the first element of the list object.
  724.  */
  725. objPtr = Tcl_NewObj();
  726. Tcl_ListObjAppendElement(NULL, objPtr,
  727. Tcl_GetObjResult(interp));
  728.     }
  729.     Tcl_ListObjAppendElement(NULL, objPtr,
  730.     Tcl_NewStringObj(Tcl_DStringValue(&ds),
  731.     Tcl_DStringLength(&ds)));
  732. }
  733. Tcl_DStringFree(&ds);
  734.     }
  735. }
  736. if (found > 1) {
  737.     Tcl_SetObjResult(interp, objPtr);
  738. }
  739.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  740.     && (length >= 3)) {
  741. char buf[200];
  742. if (argc != 3) {
  743.     Tcl_AppendResult(interp, "wrong # args: should be "",
  744.     argv[0], " index index"",
  745.     (char *) NULL);
  746.     result = TCL_ERROR;
  747.     goto done;
  748. }
  749. if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  750.     result = TCL_ERROR;
  751.     goto done;
  752. }
  753. TkTextPrintIndex(&index1, buf);
  754. Tcl_SetResult(interp, buf, TCL_VOLATILE);
  755.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  756.     && (length >= 3)) {
  757. int i, j, numTags;
  758. CONST char **tagNames;
  759. TkTextTag **oldTagArrayPtr;
  760. if (argc < 4) {
  761.     Tcl_AppendResult(interp, "wrong # args: should be "",
  762.     argv[0],
  763.     " insert index chars ?tagList chars tagList ...?"",
  764.     (char *) NULL);
  765.     result = TCL_ERROR;
  766.     goto done;
  767. }
  768. if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  769.     result = TCL_ERROR;
  770.     goto done;
  771. }
  772. if (textPtr->state == TK_STATE_NORMAL) {
  773.     for (j = 3;  j < argc; j += 2) {
  774. InsertChars(textPtr, &index1, argv[j]);
  775. if (argc > (j+1)) {
  776.     TkTextIndexForwBytes(&index1, (int) strlen(argv[j]),
  777.     &index2);
  778.     oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
  779.     if (oldTagArrayPtr != NULL) {
  780. for (i = 0; i < numTags; i++) {
  781.     TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
  782. }
  783. ckfree((char *) oldTagArrayPtr);
  784.     }
  785.     if (Tcl_SplitList(interp, argv[j+1], &numTags, &tagNames)
  786.     != TCL_OK) {
  787. result = TCL_ERROR;
  788. goto done;
  789.     }
  790.     for (i = 0; i < numTags; i++) {
  791. TkBTreeTag(&index1, &index2,
  792. TkTextCreateTag(textPtr, tagNames[i]), 1);
  793.     }
  794.     ckfree((char *) tagNames);
  795.     index1 = index2;
  796. }
  797.     }
  798. }
  799.     } else if ((c == 'd') && (strncmp(argv[1], "dump", length) == 0)) {
  800. result = TextDumpCmd(textPtr, interp, argc, argv);
  801.     } else if ((c == 'i') && (strncmp(argv[1], "image", length) == 0)) {
  802. result = TkTextImageCmd(textPtr, interp, argc, argv);
  803.     } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
  804. result = TkTextMarkCmd(textPtr, interp, argc, argv);
  805.     } else if ((c == 's') && (strcmp(argv[1], "scan") == 0) && (length >= 2)) {
  806. result = TkTextScanCmd(textPtr, interp, argc, argv);
  807.     } else if ((c == 's') && (strcmp(argv[1], "search") == 0)
  808.     && (length >= 3)) {
  809. result = TextSearchCmd(textPtr, interp, argc, argv);
  810.     } else if ((c == 's') && (strcmp(argv[1], "see") == 0) && (length >= 3)) {
  811. result = TkTextSeeCmd(textPtr, interp, argc, argv);
  812.     } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
  813. result = TkTextTagCmd(textPtr, interp, argc, argv);
  814.     } else if ((c == 'w') && (strncmp(argv[1], "window", length) == 0)) {
  815. result = TkTextWindowCmd(textPtr, interp, argc, argv);
  816.     } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
  817. result = TkTextXviewCmd(textPtr, interp, argc, argv);
  818.     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)
  819.     && (length >= 2)) {
  820. result = TkTextYviewCmd(textPtr, interp, argc, argv);
  821.     } else {
  822. Tcl_AppendResult(interp, "bad option "", argv[1],
  823. "": must be bbox, cget, compare, configure, debug, delete, ",
  824.                 "dlineinfo, dump, edit, get, image, index, insert, mark, ",
  825.                 "scan, search, see, tag, window, xview, or yview",
  826. (char *) NULL);
  827. result = TCL_ERROR;
  828.     }
  829.     done:
  830.     Tcl_Release((ClientData) textPtr);
  831.     return result;
  832. }
  833. /*
  834.  *----------------------------------------------------------------------
  835.  *
  836.  * TextIndexSortProc --
  837.  *
  838.  * This procedure is called by qsort when sorting an array of
  839.  * indices in *decreasing* order (last to first).
  840.  *
  841.  * Results:
  842.  * The return value is -1 if the first argument should be before
  843.  * the second element, 0 if it's equivalent, and 1 if it should be
  844.  * after the second element.
  845.  *
  846.  * Side effects:
  847.  * None.
  848.  *
  849.  *----------------------------------------------------------------------
  850.  */
  851. static int
  852. TextIndexSortProc(first, second)
  853.     CONST VOID *first, *second; /* Elements to be compared. */
  854. {
  855.     TkTextIndex *pair1 = (TkTextIndex *) first;
  856.     TkTextIndex *pair2 = (TkTextIndex *) second;
  857.     int cmp = TkTextIndexCmp(&pair1[1], &pair2[1]);
  858.     if (cmp == 0) {
  859. /*
  860.  * If the first indices were equal, we want the second index of the
  861.  * pair also to be the greater.  Use pointer magic to access the
  862.  * second index pair.
  863.  */
  864. cmp = TkTextIndexCmp(&pair1[0], &pair2[0]);
  865.     }
  866.     if (cmp > 0) {
  867. return -1;
  868.     } else if (cmp < 0) {
  869. return 1;
  870.     }
  871.     return 0;
  872. }
  873. /*
  874.  *----------------------------------------------------------------------
  875.  *
  876.  * DestroyText --
  877.  *
  878.  * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
  879.  * to clean up the internal structure of a text at a safe time
  880.  * (when no-one is using it anymore).
  881.  *
  882.  * Results:
  883.  * None.
  884.  *
  885.  * Side effects:
  886.  * Everything associated with the text is freed up.
  887.  *
  888.  *----------------------------------------------------------------------
  889.  */
  890. static void
  891. DestroyText(memPtr)
  892.     char *memPtr; /* Info about text widget. */
  893. {
  894.     register TkText *textPtr = (TkText *) memPtr;
  895.     Tcl_HashSearch search;
  896.     Tcl_HashEntry *hPtr;
  897.     TkTextTag *tagPtr;
  898.     /*
  899.      * Free up all the stuff that requires special handling, then
  900.      * let Tk_FreeOptions handle all the standard option-related
  901.      * stuff.  Special note:  free up display-related information
  902.      * before deleting the B-tree, since display-related stuff
  903.      * may refer to stuff in the B-tree.
  904.      */
  905.     TkTextFreeDInfo(textPtr);
  906.     TkBTreeDestroy(textPtr->tree);
  907.     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
  908.     hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  909. tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  910. TkTextFreeTag(textPtr, tagPtr);
  911.     }
  912.     Tcl_DeleteHashTable(&textPtr->tagTable);
  913.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  914.     hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  915. ckfree((char *) Tcl_GetHashValue(hPtr));
  916.     }
  917.     Tcl_DeleteHashTable(&textPtr->markTable);
  918.     if (textPtr->tabArrayPtr != NULL) {
  919. ckfree((char *) textPtr->tabArrayPtr);
  920.     }
  921.     if (textPtr->insertBlinkHandler != NULL) {
  922. Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
  923.     }
  924.     if (textPtr->bindingTable != NULL) {
  925. Tk_DeleteBindingTable(textPtr->bindingTable);
  926.     }
  927.     TkUndoFreeStack(textPtr->undoStack);
  928.     /*
  929.      * NOTE: do NOT free up selBorder, selBdString, or selFgColorPtr:
  930.      * they are duplicates of information in the "sel" tag, which was
  931.      * freed up as part of deleting the tags above.
  932.      */
  933.     textPtr->selBorder = NULL;
  934.     textPtr->selBdString = NULL;
  935.     textPtr->selFgColorPtr = NULL;
  936.     Tk_FreeOptions(configSpecs, (char *) textPtr, textPtr->display, 0);
  937.     ckfree((char *) textPtr);
  938. }
  939. /*
  940.  *----------------------------------------------------------------------
  941.  *
  942.  * ConfigureText --
  943.  *
  944.  * This procedure is called to process an argv/argc list, plus
  945.  * the Tk option database, in order to configure (or
  946.  * reconfigure) a text widget.
  947.  *
  948.  * Results:
  949.  * The return value is a standard Tcl result.  If TCL_ERROR is
  950.  * returned, then the interp's result contains an error message.
  951.  *
  952.  * Side effects:
  953.  * Configuration information, such as text string, colors, font,
  954.  * etc. get set for textPtr;  old resources get freed, if there
  955.  * were any.
  956.  *
  957.  *----------------------------------------------------------------------
  958.  */
  959. static int
  960. ConfigureText(interp, textPtr, argc, argv, flags)
  961.     Tcl_Interp *interp; /* Used for error reporting. */
  962.     register TkText *textPtr; /* Information about widget;  may or may
  963.  * not already have values for some fields. */
  964.     int argc; /* Number of valid entries in argv. */
  965.     CONST char **argv; /* Arguments. */
  966.     int flags; /* Flags to pass to Tk_ConfigureWidget. */
  967. {
  968.     int oldExport = textPtr->exportSelection;
  969.     if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
  970.     argc, argv, (char *) textPtr, flags) != TCL_OK) {
  971. return TCL_ERROR;
  972.     }
  973.     TkUndoSetDepth(textPtr->undoStack, textPtr->maxUndo);
  974.     /*
  975.      * A few other options also need special processing, such as parsing
  976.      * the geometry and setting the background from a 3-D border.
  977.      */
  978.     Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
  979.     /*
  980.      * Don't allow negative spacings.
  981.      */
  982.     if (textPtr->spacing1 < 0) {
  983. textPtr->spacing1 = 0;
  984.     }
  985.     if (textPtr->spacing2 < 0) {
  986. textPtr->spacing2 = 0;
  987.     }
  988.     if (textPtr->spacing3 < 0) {
  989. textPtr->spacing3 = 0;
  990.     }
  991.     /*
  992.      * Parse tab stops.
  993.      */
  994.     if (textPtr->tabArrayPtr != NULL) {
  995. ckfree((char *) textPtr->tabArrayPtr);
  996. textPtr->tabArrayPtr = NULL;
  997.     }
  998.     if (textPtr->tabOptionString != NULL) {
  999. textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr->tkwin,
  1000. textPtr->tabOptionString);
  1001. if (textPtr->tabArrayPtr == NULL) {
  1002.     Tcl_AddErrorInfo(interp,"n    (while processing -tabs option)");
  1003.     return TCL_ERROR;
  1004. }
  1005.     }
  1006.     /*
  1007.      * Make sure that configuration options are properly mirrored
  1008.      * between the widget record and the "sel" tags.  NOTE: we don't
  1009.      * have to free up information during the mirroring;  old
  1010.      * information was freed when it was replaced in the widget
  1011.      * record.
  1012.      */
  1013.     textPtr->selTagPtr->border = textPtr->selBorder;
  1014.     if (textPtr->selTagPtr->bdString != textPtr->selBdString) {
  1015. textPtr->selTagPtr->bdString = textPtr->selBdString;
  1016. if (textPtr->selBdString != NULL) {
  1017.     if (Tk_GetPixels(interp, textPtr->tkwin, textPtr->selBdString,
  1018.     &textPtr->selTagPtr->borderWidth) != TCL_OK) {
  1019. return TCL_ERROR;
  1020.     }
  1021.     if (textPtr->selTagPtr->borderWidth < 0) {
  1022. textPtr->selTagPtr->borderWidth = 0;
  1023.     }
  1024. }
  1025.     }
  1026.     textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
  1027.     textPtr->selTagPtr->affectsDisplay = 0;
  1028.     if ((textPtr->selTagPtr->border != NULL)
  1029.     || (textPtr->selTagPtr->bdString != NULL)
  1030.     || (textPtr->selTagPtr->reliefString != NULL)
  1031.     || (textPtr->selTagPtr->bgStipple != None)
  1032.     || (textPtr->selTagPtr->fgColor != NULL)
  1033.     || (textPtr->selTagPtr->tkfont != None)
  1034.     || (textPtr->selTagPtr->fgStipple != None)
  1035.     || (textPtr->selTagPtr->justifyString != NULL)
  1036.     || (textPtr->selTagPtr->lMargin1String != NULL)
  1037.     || (textPtr->selTagPtr->lMargin2String != NULL)
  1038.     || (textPtr->selTagPtr->offsetString != NULL)
  1039.     || (textPtr->selTagPtr->overstrikeString != NULL)
  1040.     || (textPtr->selTagPtr->rMarginString != NULL)
  1041.     || (textPtr->selTagPtr->spacing1String != NULL)
  1042.     || (textPtr->selTagPtr->spacing2String != NULL)
  1043.     || (textPtr->selTagPtr->spacing3String != NULL)
  1044.     || (textPtr->selTagPtr->tabString != NULL)
  1045.     || (textPtr->selTagPtr->underlineString != NULL)
  1046.     || (textPtr->selTagPtr->elideString != NULL)
  1047.     || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
  1048. textPtr->selTagPtr->affectsDisplay = 1;
  1049.     }
  1050.     TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
  1051.     textPtr->selTagPtr, 1);
  1052.     /*
  1053.      * Claim the selection if we've suddenly started exporting it and there
  1054.      * are tagged characters.
  1055.      */
  1056.     if (textPtr->exportSelection && (!oldExport)) {
  1057. TkTextSearch search;
  1058. TkTextIndex first, last;
  1059. TkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
  1060. TkTextMakeByteIndex(textPtr->tree,
  1061. TkBTreeNumLines(textPtr->tree), 0, &last);
  1062. TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search);
  1063. if (TkBTreeCharTagged(&first, textPtr->selTagPtr)
  1064. || TkBTreeNextTag(&search)) {
  1065.     Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection,
  1066.     (ClientData) textPtr);
  1067.     textPtr->flags |= GOT_SELECTION;
  1068. }
  1069.     }
  1070.     /*
  1071.      * Account for state changes that would reenable blinking cursor state.
  1072.      */
  1073.     if (textPtr->flags & GOT_FOCUS) {
  1074. Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
  1075. textPtr->insertBlinkHandler = (Tcl_TimerToken) NULL;
  1076. TextBlinkProc((ClientData) textPtr);
  1077.     }
  1078.     /*
  1079.      * Register the desired geometry for the window, and arrange for
  1080.      * the window to be redisplayed.
  1081.      */
  1082.     if (textPtr->width <= 0) {
  1083. textPtr->width = 1;
  1084.     }
  1085.     if (textPtr->height <= 0) {
  1086. textPtr->height = 1;
  1087.     }
  1088.     TextWorldChanged((ClientData) textPtr);
  1089.     return TCL_OK;
  1090. }
  1091. /*
  1092.  *---------------------------------------------------------------------------
  1093.  *
  1094.  * TextWorldChanged --
  1095.  *
  1096.  *      This procedure is called when the world has changed in some
  1097.  *      way and the widget needs to recompute all its graphics contexts
  1098.  * and determine its new geometry.
  1099.  *
  1100.  * Results:
  1101.  *      None.
  1102.  *
  1103.  * Side effects:
  1104.  * Configures all tags in the Text with a empty argc/argv, for
  1105.  * the side effect of causing all the items to recompute their
  1106.  * geometry and to be redisplayed.
  1107.  *
  1108.  *---------------------------------------------------------------------------
  1109.  */
  1110.  
  1111. static void
  1112. TextWorldChanged(instanceData)
  1113.     ClientData instanceData; /* Information about widget. */
  1114. {
  1115.     TkText *textPtr;
  1116.     Tk_FontMetrics fm;
  1117.     textPtr = (TkText *) instanceData;
  1118.     textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
  1119.     if (textPtr->charWidth <= 0) {
  1120. textPtr->charWidth = 1;
  1121.     }
  1122.     Tk_GetFontMetrics(textPtr->tkfont, &fm);
  1123.     Tk_GeometryRequest(textPtr->tkwin,
  1124.     textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth
  1125.     + 2*textPtr->padX + 2*textPtr->highlightWidth,
  1126.     textPtr->height * (fm.linespace + textPtr->spacing1
  1127.     + textPtr->spacing3) + 2*textPtr->borderWidth
  1128.     + 2*textPtr->padY + 2*textPtr->highlightWidth);
  1129.     Tk_SetInternalBorder(textPtr->tkwin,
  1130.     textPtr->borderWidth + textPtr->highlightWidth);
  1131.     if (textPtr->setGrid) {
  1132. Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
  1133. textPtr->charWidth, fm.linespace);
  1134.     } else {
  1135. Tk_UnsetGrid(textPtr->tkwin);
  1136.     }
  1137.     TkTextRelayoutWindow(textPtr);
  1138. }
  1139. /*
  1140.  *--------------------------------------------------------------
  1141.  *
  1142.  * TextEventProc --
  1143.  *
  1144.  * This procedure is invoked by the Tk dispatcher on
  1145.  * structure changes to a text.  For texts with 3D
  1146.  * borders, this procedure is also invoked for exposures.
  1147.  *
  1148.  * Results:
  1149.  * None.
  1150.  *
  1151.  * Side effects:
  1152.  * When the window gets deleted, internal structures get
  1153.  * cleaned up.  When it gets exposed, it is redisplayed.
  1154.  *
  1155.  *--------------------------------------------------------------
  1156.  */
  1157. static void
  1158. TextEventProc(clientData, eventPtr)
  1159.     ClientData clientData; /* Information about window. */
  1160.     register XEvent *eventPtr; /* Information about event. */
  1161. {
  1162.     register TkText *textPtr = (TkText *) clientData;
  1163.     TkTextIndex index, index2;
  1164.     if (eventPtr->type == Expose) {
  1165. TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
  1166. eventPtr->xexpose.y, eventPtr->xexpose.width,
  1167. eventPtr->xexpose.height);
  1168.     } else if (eventPtr->type == ConfigureNotify) {
  1169. if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
  1170. || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
  1171.     TkTextRelayoutWindow(textPtr);
  1172.     textPtr->prevWidth = Tk_Width(textPtr->tkwin);
  1173.     textPtr->prevHeight = Tk_Height(textPtr->tkwin);
  1174. }
  1175.     } else if (eventPtr->type == DestroyNotify) {
  1176. if (textPtr->tkwin != NULL) {
  1177.     if (textPtr->setGrid) {
  1178. Tk_UnsetGrid(textPtr->tkwin);
  1179.     }
  1180.     textPtr->tkwin = NULL;
  1181.     Tcl_DeleteCommandFromToken(textPtr->interp,
  1182.     textPtr->widgetCmd);
  1183. }
  1184. Tcl_EventuallyFree((ClientData) textPtr, DestroyText);
  1185.     } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
  1186. if (eventPtr->xfocus.detail != NotifyInferior) {
  1187.     Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
  1188.     if (eventPtr->type == FocusIn) {
  1189. textPtr->flags |= GOT_FOCUS | INSERT_ON;
  1190. if (textPtr->insertOffTime != 0) {
  1191.     textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
  1192.     textPtr->insertOnTime, TextBlinkProc,
  1193.     (ClientData) textPtr);
  1194. }
  1195.     } else {
  1196. textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
  1197. textPtr->insertBlinkHandler = (Tcl_TimerToken) NULL;
  1198.     }
  1199.     if (
  1200. #ifndef MAC_OSX_TK
  1201.     !TkpAlwaysShowSelection(textPtr->tkwin)
  1202. #else
  1203.     /* Don't show inactive selection in disabled widgets. */
  1204.     textPtr->state != TK_STATE_DISABLED
  1205. #endif
  1206.     ) {
  1207. TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1);
  1208.     }
  1209.     TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
  1210.     TkTextIndexForwChars(&index, 1, &index2);
  1211.     TkTextChanged(textPtr, &index, &index2);
  1212.     if (textPtr->highlightWidth > 0) {
  1213. TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
  1214. textPtr->highlightWidth);
  1215.     }
  1216. }
  1217.     }
  1218. }
  1219. /*
  1220.  *----------------------------------------------------------------------
  1221.  *
  1222.  * TextCmdDeletedProc --
  1223.  *
  1224.  * This procedure is invoked when a widget command is deleted.  If
  1225.  * the widget isn't already in the process of being destroyed,
  1226.  * this command destroys it.
  1227.  *
  1228.  * Results:
  1229.  * None.
  1230.  *
  1231.  * Side effects:
  1232.  * The widget is destroyed.
  1233.  *
  1234.  *----------------------------------------------------------------------
  1235.  */
  1236. static void
  1237. TextCmdDeletedProc(clientData)
  1238.     ClientData clientData; /* Pointer to widget record for widget. */
  1239. {
  1240.     TkText *textPtr = (TkText *) clientData;
  1241.     Tk_Window tkwin = textPtr->tkwin;
  1242.     /*
  1243.      * This procedure could be invoked either because the window was
  1244.      * destroyed and the command was then deleted (in which case tkwin
  1245.      * is NULL) or because the command was deleted, and then this procedure
  1246.      * destroys the widget.
  1247.      */
  1248.     if (tkwin != NULL) {
  1249. if (textPtr->setGrid) {
  1250.     Tk_UnsetGrid(textPtr->tkwin);
  1251. }
  1252. textPtr->tkwin = NULL;
  1253. Tk_DestroyWindow(tkwin);
  1254.     }
  1255. }
  1256. /*
  1257.  *----------------------------------------------------------------------
  1258.  *
  1259.  * InsertChars --
  1260.  *
  1261.  * This procedure implements most of the functionality of the
  1262.  * "insert" widget command.
  1263.  *
  1264.  * Results:
  1265.  * None.
  1266.  *
  1267.  * Side effects:
  1268.  * The characters in "string" get added to the text just before
  1269.  * the character indicated by "indexPtr".
  1270.  *
  1271.  *----------------------------------------------------------------------
  1272.  */
  1273. static void
  1274. InsertChars(textPtr, indexPtr, string)
  1275.     TkText *textPtr; /* Overall information about text widget. */
  1276.     TkTextIndex *indexPtr; /* Where to insert new characters.  May be
  1277.  * modified and/or invalidated. */
  1278.     CONST char *string; /* Null-terminated string containing new
  1279.  * information to add to text. */
  1280. {
  1281.     int lineIndex, resetView, offset;
  1282.     TkTextIndex newTop;
  1283.     char indexBuffer[TK_POS_CHARS];
  1284.     /*
  1285.      * Don't do anything for an empty string [Bug 1275237]
  1286.      */
  1287.     if (*string == '') {
  1288. return;
  1289.     }
  1290.     /*
  1291.      * Don't allow insertions on the last (dummy) line of the text.
  1292.      */
  1293.     lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
  1294.     if (lineIndex == TkBTreeNumLines(textPtr->tree)) {
  1295. lineIndex--;
  1296. TkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
  1297.     }
  1298.     /*
  1299.      * Notify the display module that lines are about to change, then do
  1300.      * the insertion.  If the insertion occurs on the top line of the
  1301.      * widget (textPtr->topIndex), then we have to recompute topIndex
  1302.      * after the insertion, since the insertion could invalidate it.
  1303.      */
  1304.     resetView = offset = 0;
  1305.     if (indexPtr->linePtr == textPtr->topIndex.linePtr) {
  1306. resetView = 1;
  1307. offset = textPtr->topIndex.byteIndex;
  1308. if (offset > indexPtr->byteIndex) {
  1309.     offset += strlen(string);
  1310. }
  1311.     }
  1312.     TkTextChanged(textPtr, indexPtr, indexPtr);
  1313.     TkBTreeInsertChars(indexPtr, string);
  1314.     /*
  1315.      * Push the insertion on the undo stack
  1316.      */
  1317.     if (textPtr->undo) {
  1318. CONST char *cmdName;
  1319. TkTextIndex toIndex;
  1320. Tcl_DString actionCommand;
  1321. Tcl_DString revertCommand;
  1322. if (textPtr->autoSeparators &&
  1323. textPtr->lastEditMode != TK_TEXT_EDIT_INSERT) {
  1324.     TkUndoInsertUndoSeparator(textPtr->undoStack);
  1325. }
  1326. textPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
  1327. cmdName = Tcl_GetCommandName(textPtr->interp, textPtr->widgetCmd);
  1328. Tcl_DStringInit(&actionCommand);
  1329. Tcl_DStringInit(&revertCommand);
  1330. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1331. Tcl_DStringAppend(&actionCommand, " insert ", -1);
  1332. TkTextPrintIndex(indexPtr,indexBuffer);
  1333. Tcl_DStringAppend(&actionCommand, indexBuffer, -1);
  1334. Tcl_DStringAppend(&actionCommand, " ", -1);
  1335. Tcl_DStringAppendElement(&actionCommand, string);
  1336. Tcl_DStringAppend(&actionCommand, ";", -1);
  1337. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1338. Tcl_DStringAppend(&actionCommand, " mark set insert ", -1);
  1339. TkTextIndexForwBytes(indexPtr, (int) strlen(string), &toIndex);
  1340. TkTextPrintIndex(&toIndex, indexBuffer);
  1341. Tcl_DStringAppend(&actionCommand, indexBuffer,-1);
  1342. Tcl_DStringAppend(&actionCommand, "; ", -1);
  1343. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1344. Tcl_DStringAppend(&actionCommand, " see insert", -1);
  1345. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1346. Tcl_DStringAppend(&revertCommand, " delete ", -1);
  1347. TkTextPrintIndex(indexPtr, indexBuffer);
  1348. Tcl_DStringAppend(&revertCommand, indexBuffer, -1);
  1349. Tcl_DStringAppend(&revertCommand, " ", -1);
  1350. TkTextPrintIndex(&toIndex, indexBuffer);
  1351. Tcl_DStringAppend(&revertCommand, indexBuffer, -1);
  1352. Tcl_DStringAppend(&revertCommand, " ;", -1);
  1353. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1354. Tcl_DStringAppend(&revertCommand, " mark set insert ", -1);
  1355. TkTextPrintIndex(indexPtr,indexBuffer);
  1356. Tcl_DStringAppend(&revertCommand, indexBuffer, -1);
  1357. Tcl_DStringAppend(&revertCommand, "; ", -1);
  1358. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1359. Tcl_DStringAppend(&revertCommand," see insert", -1);
  1360. TkUndoPushAction(textPtr->undoStack, &actionCommand, &revertCommand);
  1361. Tcl_DStringFree(&actionCommand);
  1362. Tcl_DStringFree(&revertCommand);
  1363.     }
  1364.     updateDirtyFlag(textPtr);
  1365.     if (resetView) {
  1366. TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop);
  1367. TkTextIndexForwBytes(&newTop, offset, &newTop);
  1368. TkTextSetYView(textPtr, &newTop, 0);
  1369.     }
  1370.     /*
  1371.      * Invalidate any selection retrievals in progress.
  1372.      */
  1373.     textPtr->abortSelections = 1;
  1374. }
  1375. /*
  1376.  *----------------------------------------------------------------------
  1377.  *
  1378.  * DeleteChars --
  1379.  *
  1380.  * This procedure implements most of the functionality of the
  1381.  * "delete" widget command.
  1382.  *
  1383.  * Results:
  1384.  * Returns a standard Tcl result, and leaves an error message
  1385.  * in textPtr->interp if there is an error.
  1386.  *
  1387.  * Side effects:
  1388.  * Characters get deleted from the text.
  1389.  *
  1390.  *----------------------------------------------------------------------
  1391.  */
  1392. static int
  1393. DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2)
  1394.     TkText *textPtr; /* Overall information about text widget. */
  1395.     CONST char *index1String; /* String describing location of first
  1396.  * character to delete. */
  1397.     CONST char *index2String; /* String describing location of last
  1398.  * character to delete.  NULL means just
  1399.  * delete the one character given by
  1400.  * index1String. */
  1401.     TkTextIndex *indexPtr1; /* index describing location of first
  1402.  * character to delete. */
  1403.     TkTextIndex *indexPtr2; /* index describing location of last
  1404.  * character to delete.  NULL means just
  1405.  * delete the one character given by
  1406.  * indexPtr1. */
  1407. {
  1408.     int line1, line2, line, byteIndex, resetView;
  1409.     TkTextIndex index1, index2;
  1410.     char indexBuffer[TK_POS_CHARS];
  1411.     /*
  1412.      * Parse the starting and stopping indices.
  1413.      */
  1414.     if (index1String != NULL) {
  1415. if (TkTextGetIndex(textPtr->interp, textPtr, index1String, &index1)
  1416. != TCL_OK) {
  1417.     return TCL_ERROR;
  1418. }
  1419. if (index2String != NULL) {
  1420.     if (TkTextGetIndex(textPtr->interp, textPtr, index2String, &index2)
  1421.     != TCL_OK) {
  1422. return TCL_ERROR;
  1423.     }
  1424. } else {
  1425.     index2 = index1;
  1426.     TkTextIndexForwChars(&index2, 1, &index2);
  1427. }
  1428.     } else {
  1429. index1 = *indexPtr1;
  1430. if (indexPtr2 != NULL) {
  1431.     index2 = *indexPtr2;
  1432. } else {
  1433.     index2 = index1;
  1434.     TkTextIndexForwChars(&index2, 1, &index2);
  1435. }
  1436.     }
  1437.     /*
  1438.      * Make sure there's really something to delete.
  1439.      */
  1440.     if (TkTextIndexCmp(&index1, &index2) >= 0) {
  1441. return TCL_OK;
  1442.     }
  1443.     /*
  1444.      * The code below is ugly, but it's needed to make sure there
  1445.      * is always a dummy empty line at the end of the text.  If the
  1446.      * final newline of the file (just before the dummy line) is being
  1447.      * deleted, then back up index to just before the newline.  If
  1448.      * there is a newline just before the first character being deleted,
  1449.      * then back up the first index too, so that an even number of lines
  1450.      * gets deleted.  Furthermore, remove any tags that are present on
  1451.      * the newline that isn't going to be deleted after all (this simulates
  1452.      * deleting the newline and then adding a "clean" one back again).
  1453.      */
  1454.     line1 = TkBTreeLineIndex(index1.linePtr);
  1455.     line2 = TkBTreeLineIndex(index2.linePtr);
  1456.     if (line2 == TkBTreeNumLines(textPtr->tree)) {
  1457. TkTextTag **arrayPtr;
  1458. int arraySize, i;
  1459. TkTextIndex oldIndex2;
  1460. oldIndex2 = index2;
  1461. TkTextIndexBackChars(&oldIndex2, 1, &index2);
  1462. line2--;
  1463. if ((index1.byteIndex == 0) && (line1 != 0)) {
  1464.     TkTextIndexBackChars(&index1, 1, &index1);
  1465.     line1--;
  1466. }
  1467. arrayPtr = TkBTreeGetTags(&index2, &arraySize);
  1468. if (arrayPtr != NULL) {
  1469.     for (i = 0; i < arraySize; i++) {
  1470. TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
  1471.     }
  1472.     ckfree((char *) arrayPtr);
  1473. }
  1474.     }
  1475.     if (line1 < line2) {
  1476. /*
  1477.  * We are deleting more than one line. For speed, we remove all tags
  1478.  * from the range first. If we don't do this, the code below can (when
  1479.  * there are many tags) grow non-linearly in execution time.
  1480.  * [Bug 1456342]
  1481.  */
  1482. Tcl_HashSearch search;
  1483. Tcl_HashEntry *hPtr;
  1484. for (hPtr=Tcl_FirstHashEntry(&textPtr->tagTable, &search);
  1485.      hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  1486.     TkTextTag *tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  1487.     TkBTreeTag(&index1, &index2, tagPtr, 0);
  1488. }
  1489.     }
  1490.     /*
  1491.      * Tell the display what's about to happen so it can discard
  1492.      * obsolete display information, then do the deletion.  Also,
  1493.      * if the deletion involves the top line on the screen, then
  1494.      * we have to reset the view (the deletion will invalidate
  1495.      * textPtr->topIndex).  Compute what the new first character
  1496.      * will be, then do the deletion, then reset the view.
  1497.      */
  1498.     TkTextChanged(textPtr, &index1, &index2);
  1499.     resetView = 0;
  1500.     line = 0;
  1501.     byteIndex = 0;
  1502.     if (TkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) {
  1503. if (TkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) {
  1504.     /*
  1505.      * Deletion range straddles topIndex: use the beginning
  1506.      * of the range as the new topIndex.
  1507.      */
  1508.     resetView = 1;
  1509.     line = line1;
  1510.     byteIndex = index1.byteIndex;
  1511. } else if (index1.linePtr == textPtr->topIndex.linePtr) {
  1512.     /*
  1513.      * Deletion range starts on top line but after topIndex.
  1514.      * Use the current topIndex as the new one.
  1515.      */
  1516.     resetView = 1;
  1517.     line = line1;
  1518.     byteIndex = textPtr->topIndex.byteIndex;
  1519. }
  1520.     } else if (index2.linePtr == textPtr->topIndex.linePtr) {
  1521. /*
  1522.  * Deletion range ends on top line but before topIndex.
  1523.  * Figure out what will be the new character index for
  1524.  * the character currently pointed to by topIndex.
  1525.  */
  1526. resetView = 1;
  1527. line = line2;
  1528. byteIndex = textPtr->topIndex.byteIndex;
  1529. if (index1.linePtr != index2.linePtr) {
  1530.     byteIndex -= index2.byteIndex;
  1531. } else {
  1532.     byteIndex -= (index2.byteIndex - index1.byteIndex);
  1533. }
  1534.     }
  1535.     /*
  1536.      * Push the deletion on the undo stack
  1537.      */
  1538.     if (textPtr->undo) {
  1539. CONST char *cmdName;
  1540. Tcl_DString ds;
  1541. Tcl_DString actionCommand;
  1542. Tcl_DString revertCommand;
  1543. if (textPtr->autoSeparators
  1544. && (textPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) {
  1545.    TkUndoInsertUndoSeparator(textPtr->undoStack);
  1546. }
  1547. textPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
  1548. cmdName = Tcl_GetCommandName(textPtr->interp, textPtr->widgetCmd);
  1549. Tcl_DStringInit(&actionCommand);
  1550. Tcl_DStringInit(&revertCommand);
  1551. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1552. Tcl_DStringAppend(&actionCommand, " delete ", -1);
  1553. TkTextPrintIndex(&index1, indexBuffer);
  1554. Tcl_DStringAppend(&actionCommand, indexBuffer, -1);
  1555. Tcl_DStringAppend(&actionCommand, " ", -1);
  1556. TkTextPrintIndex(&index2, indexBuffer);
  1557. Tcl_DStringAppend(&actionCommand, indexBuffer, -1);
  1558. Tcl_DStringAppend(&actionCommand, "; ", -1);
  1559. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1560. Tcl_DStringAppend(&actionCommand, " mark set insert ", -1);
  1561. TkTextPrintIndex(&index1, indexBuffer);
  1562. Tcl_DStringAppend(&actionCommand,indexBuffer, -1);
  1563. Tcl_DStringAppend(&actionCommand, "; ", -1);
  1564. Tcl_DStringAppendElement(&actionCommand, cmdName);
  1565. Tcl_DStringAppend(&actionCommand, " see insert", -1);
  1566. TextGetText(&index1, &index2, &ds);
  1567. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1568. Tcl_DStringAppend(&revertCommand, " insert ", -1);
  1569. TkTextPrintIndex(&index1, indexBuffer);
  1570. Tcl_DStringAppend(&revertCommand, indexBuffer, -1);
  1571. Tcl_DStringAppend(&revertCommand, " ", -1);
  1572. Tcl_DStringAppendElement(&revertCommand, Tcl_DStringValue(&ds));
  1573. Tcl_DStringAppend(&revertCommand, "; ", -1);
  1574. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1575. Tcl_DStringAppend(&revertCommand, " mark set insert ", -1);
  1576. TkTextPrintIndex(&index2, indexBuffer);
  1577. Tcl_DStringAppend(&revertCommand, indexBuffer, -1);
  1578. Tcl_DStringAppend(&revertCommand, "; ", -1);
  1579. Tcl_DStringAppendElement(&revertCommand, cmdName);
  1580. Tcl_DStringAppend(&revertCommand, " see insert", -1);
  1581. TkUndoPushAction(textPtr->undoStack, &actionCommand, &revertCommand);
  1582. Tcl_DStringFree(&actionCommand);
  1583. Tcl_DStringFree(&revertCommand);
  1584.     }
  1585.     TkBTreeDeleteChars(&index1, &index2);
  1586.     updateDirtyFlag(textPtr);
  1587.     if (resetView) {
  1588. TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1);
  1589. TkTextSetYView(textPtr, &index1, 0);
  1590.     }
  1591.     /*
  1592.      * Invalidate any selection retrievals in progress.
  1593.      */
  1594.     textPtr->abortSelections = 1;
  1595.     return TCL_OK;
  1596. }
  1597. /*
  1598.  *----------------------------------------------------------------------
  1599.  *
  1600.  * TextFetchSelection --
  1601.  *
  1602.  * This procedure is called back by Tk when the selection is
  1603.  * requested by someone.  It returns part or all of the selection
  1604.  * in a buffer provided by the caller.
  1605.  *
  1606.  * Results:
  1607.  * The return value is the number of non-NULL bytes stored
  1608.  * at buffer.  Buffer is filled (or partially filled) with a
  1609.  * NULL-terminated string containing part or all of the selection,
  1610.  * as given by offset and maxBytes.
  1611.  *
  1612.  * Side effects:
  1613.  * None.
  1614.  *
  1615.  *----------------------------------------------------------------------
  1616.  */
  1617. static int
  1618. TextFetchSelection(clientData, offset, buffer, maxBytes)
  1619.     ClientData clientData; /* Information about text widget. */
  1620.     int offset; /* Offset within selection of first
  1621.  * character to be returned. */
  1622.     char *buffer; /* Location in which to place
  1623.  * selection. */
  1624.     int maxBytes; /* Maximum number of bytes to place
  1625.  * at buffer, not including terminating
  1626.  * NULL character. */
  1627. {
  1628.     register TkText *textPtr = (TkText *) clientData;
  1629.     TkTextIndex eof;
  1630.     int count, chunkSize, offsetInSeg;
  1631.     TkTextSearch search;
  1632.     TkTextSegment *segPtr;
  1633.     if (!textPtr->exportSelection) {
  1634. return -1;
  1635.     }
  1636.     /*
  1637.      * Find the beginning of the next range of selected text.  Note:  if
  1638.      * the selection is being retrieved in multiple pieces (offset != 0)
  1639.      * and some modification has been made to the text that affects the
  1640.      * selection then reject the selection request (make 'em start over
  1641.      * again).
  1642.      */
  1643.     if (offset == 0) {
  1644. TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->selIndex);
  1645. textPtr->abortSelections = 0;
  1646.     } else if (textPtr->abortSelections) {
  1647. return 0;
  1648.     }
  1649.     TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &eof);
  1650.     TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, &search);
  1651.     if (!TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) {
  1652. if (!TkBTreeNextTag(&search)) {
  1653.     if (offset == 0) {
  1654. return -1;
  1655.     } else {
  1656. return 0;
  1657.     }
  1658. }
  1659. textPtr->selIndex = search.curIndex;
  1660.     }
  1661.     /*
  1662.      * Each iteration through the outer loop below scans one selected range.
  1663.      * Each iteration through the inner loop scans one segment in the
  1664.      * selected range.
  1665.      */
  1666.     count = 0;
  1667.     while (1) {
  1668. /*
  1669.  * Find the end of the current range of selected text.
  1670.  */
  1671. if (!TkBTreeNextTag(&search)) {
  1672.     panic("TextFetchSelection couldn't find end of range");
  1673. }
  1674. /*
  1675.  * Copy information from character segments into the buffer
  1676.  * until either we run out of space in the buffer or we get
  1677.  * to the end of this range of text.
  1678.  */
  1679. while (1) {
  1680.     if (maxBytes == 0) {
  1681. goto done;
  1682.     }
  1683.     segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg);
  1684.     chunkSize = segPtr->size - offsetInSeg;
  1685.     if (chunkSize > maxBytes) {
  1686. chunkSize = maxBytes;
  1687.     }
  1688.     if (textPtr->selIndex.linePtr == search.curIndex.linePtr) {
  1689. int leftInRange;
  1690. leftInRange = search.curIndex.byteIndex
  1691. - textPtr->selIndex.byteIndex;
  1692. if (leftInRange < chunkSize) {
  1693.     chunkSize = leftInRange;
  1694.     if (chunkSize <= 0) {
  1695. break;
  1696.     }
  1697. }
  1698.     }
  1699.     if ((segPtr->typePtr == &tkTextCharType)
  1700.     && !TkTextIsElided(textPtr, &textPtr->selIndex)) {
  1701. memcpy((VOID *) buffer, (VOID *) (segPtr->body.chars
  1702. + offsetInSeg), (size_t) chunkSize);
  1703. buffer += chunkSize;
  1704. maxBytes -= chunkSize;
  1705. count += chunkSize;
  1706.     }
  1707.     TkTextIndexForwBytes(&textPtr->selIndex, chunkSize,
  1708.     &textPtr->selIndex);
  1709. }
  1710. /*
  1711.  * Find the beginning of the next range of selected text.
  1712.  */
  1713. if (!TkBTreeNextTag(&search)) {
  1714.     break;
  1715. }
  1716. textPtr->selIndex = search.curIndex;
  1717.     }
  1718.     done:
  1719.     *buffer = 0;
  1720.     return count;
  1721. }
  1722. /*
  1723.  *----------------------------------------------------------------------
  1724.  *
  1725.  * TkTextLostSelection --
  1726.  *
  1727.  * This procedure is called back by Tk when the selection is
  1728.  * grabbed away from a text widget.  On Windows and Mac systems, we
  1729.  * want to remember the selection for the next time the focus
  1730.  * enters the window.  On Unix, just remove the "sel" tag from
  1731.  * everything in the widget.
  1732.  *
  1733.  * Results:
  1734.  * None.
  1735.  *
  1736.  * Side effects:
  1737.  * The "sel" tag is cleared from the window.
  1738.  *
  1739.  *----------------------------------------------------------------------
  1740.  */
  1741. void
  1742. TkTextLostSelection(clientData)
  1743.     ClientData clientData; /* Information about text widget. */
  1744. {
  1745.     register TkText *textPtr = (TkText *) clientData;
  1746.     XEvent event;
  1747.     if (TkpAlwaysShowSelection(textPtr->tkwin)) {
  1748. TkTextIndex start, end;
  1749. if (!textPtr->exportSelection) {
  1750.     return;
  1751. }
  1752. /*
  1753.  * On Windows and Mac systems, we want to remember the selection
  1754.  * for the next time the focus enters the window.  On Unix, 
  1755.  * just remove the "sel" tag from everything in the widget.
  1756.  */
  1757. TkTextMakeByteIndex(textPtr->tree, 0, 0, &start);
  1758. TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &end);
  1759. TkTextRedrawTag(textPtr, &start, &end, textPtr->selTagPtr, 1);
  1760. TkBTreeTag(&start, &end, textPtr->selTagPtr, 0);
  1761.     }
  1762.     /*
  1763.      * Send an event that the selection changed.  This is equivalent to
  1764.      * "event generate $textWidget <<Selection>>"
  1765.      */
  1766.     memset((VOID *) &event, 0, sizeof(event));
  1767.     event.xany.type = VirtualEvent;
  1768.     event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
  1769.     event.xany.send_event = False;
  1770.     event.xany.window = Tk_WindowId(textPtr->tkwin);
  1771.     event.xany.display = Tk_Display(textPtr->tkwin);
  1772.     ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
  1773.     Tk_HandleEvent(&event);
  1774.     textPtr->flags &= ~GOT_SELECTION;
  1775. }
  1776. /*
  1777.  *----------------------------------------------------------------------
  1778.  *
  1779.  * TextBlinkProc --
  1780.  *
  1781.  * This procedure is called as a timer handler to blink the
  1782.  * insertion cursor off and on.
  1783.  *
  1784.  * Results:
  1785.  * None.
  1786.  *
  1787.  * Side effects:
  1788.  * The cursor gets turned on or off, redisplay gets invoked,
  1789.  * and this procedure reschedules itself.
  1790.  *
  1791.  *----------------------------------------------------------------------
  1792.  */
  1793. static void
  1794. TextBlinkProc(clientData)
  1795.     ClientData clientData; /* Pointer to record describing text. */
  1796. {
  1797.     register TkText *textPtr = (TkText *) clientData;
  1798.     TkTextIndex index;
  1799.     int x, y, w, h;
  1800.     if ((textPtr->state == TK_STATE_DISABLED) ||
  1801.     !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
  1802. return;
  1803.     }
  1804.     if (textPtr->flags & INSERT_ON) {
  1805. textPtr->flags &= ~INSERT_ON;
  1806. textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
  1807. textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
  1808.     } else {
  1809. textPtr->flags |= INSERT_ON;
  1810. textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
  1811. textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
  1812.     }
  1813.     TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
  1814.     if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h) == 0) {
  1815. TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
  1816. textPtr->insertWidth, h);
  1817.     }
  1818. }
  1819. /*
  1820.  *----------------------------------------------------------------------
  1821.  *
  1822.  * TextSearchCmd --
  1823.  *
  1824.  * This procedure is invoked to process the "search" widget command
  1825.  * for text widgets.  See the user documentation for details on what
  1826.  * it does.
  1827.  *
  1828.  * Results:
  1829.  * A standard Tcl result.
  1830.  *
  1831.  * Side effects:
  1832.  * See the user documentation.
  1833.  *
  1834.  *----------------------------------------------------------------------
  1835.  */
  1836. static int
  1837. TextSearchCmd(textPtr, interp, argc, argv)
  1838.     TkText *textPtr; /* Information about text widget. */
  1839.     Tcl_Interp *interp; /* Current interpreter. */
  1840.     int argc; /* Number of arguments. */
  1841.     CONST char **argv; /* Argument strings. */
  1842. {
  1843.     int backwards, exact, searchElide, c, i, argsLeft, noCase, leftToScan;
  1844.     size_t length;
  1845.     int numLines, startingLine, startingByte, lineNum, firstByte, lastByte;
  1846.     int code, matchLength, matchByte, passes, stopLine, searchWholeText;
  1847.     int patLength;
  1848.     CONST char *arg, *pattern, *varName, *p, *startOfLine;
  1849.     char buffer[20];
  1850.     TkTextIndex index, stopIndex;
  1851.     Tcl_DString line, patDString;
  1852.     TkTextSegment *segPtr;
  1853.     TkTextLine *linePtr;
  1854.     TkTextIndex curIndex;
  1855.     Tcl_Obj *patObj = NULL;
  1856.     Tcl_RegExp regexp = NULL; /* Initialization needed only to
  1857.  * prevent compiler warning. */
  1858.     /*
  1859.      * Parse switches and other arguments.
  1860.      */
  1861.     exact = 1;
  1862.     searchElide = 0;
  1863.     curIndex.tree = textPtr->tree;
  1864.     backwards = 0;
  1865.     noCase = 0;
  1866.     varName = NULL;
  1867.     for (i = 2; i < argc; i++) {
  1868. arg = argv[i];
  1869. if (arg[0] != '-') {
  1870.     break;
  1871. }
  1872. length = strlen(arg);
  1873. if (length < 2) {
  1874.     badSwitch:
  1875.     Tcl_AppendResult(interp, "bad switch "", arg,
  1876.     "": must be --, -backward, -count, -elide, -exact, ",
  1877.     "-forward, -nocase, or -regexp", (char *) NULL);
  1878.     return TCL_ERROR;
  1879. }
  1880. c = arg[1];
  1881. if ((c == 'b') && (strncmp(argv[i], "-backwards", length) == 0)) {
  1882.     backwards = 1;
  1883. } else if ((c == 'c') && (strncmp(argv[i], "-count", length) == 0)) {
  1884.     if (i >= (argc-1)) {
  1885. Tcl_SetResult(interp, "no value given for "-count" option",
  1886. TCL_STATIC);
  1887. return TCL_ERROR;
  1888.     }
  1889.     i++;
  1890.     varName = argv[i];
  1891. } else if ((c == 'e') && (length > 2)
  1892. && (strncmp(argv[i], "-exact", length) == 0)) {
  1893.     exact = 1;
  1894. } else if ((c == 'e') && (length > 2)
  1895. && (strncmp(argv[i], "-elide", length) == 0)) {
  1896.     searchElide = 1;
  1897. } else if ((c == 'h') && (strncmp(argv[i], "-hidden", length) == 0)) {
  1898.     /*
  1899.      * -hidden is kept around for backwards compatibility with
  1900.      * the dash patch, but -elide is the official option
  1901.      */
  1902.     searchElide = 1;
  1903. } else if ((c == 'f') && (strncmp(argv[i], "-forwards", length) == 0)) {
  1904.     backwards = 0;
  1905. } else if ((c == 'n') && (strncmp(argv[i], "-nocase", length) == 0)) {
  1906.     noCase = 1;
  1907. } else if ((c == 'r') && (strncmp(argv[i], "-regexp", length) == 0)) {
  1908.     exact = 0;
  1909. } else if ((c == '-') && (strncmp(argv[i], "--", length) == 0)) {
  1910.     i++;
  1911.     break;
  1912. } else {
  1913.     goto badSwitch;
  1914. }
  1915.     }
  1916.     argsLeft = argc - (i+2);
  1917.     if ((argsLeft != 0) && (argsLeft != 1)) {
  1918. Tcl_AppendResult(interp, "wrong # args: should be "",
  1919. argv[0], " search ?switches? pattern index ?stopIndex?"",
  1920. (char *) NULL);
  1921. return TCL_ERROR;
  1922.     }
  1923.     pattern = argv[i];
  1924.     /*
  1925.      * Convert the pattern to lower-case if we're supposed to ignore case.
  1926.      */
  1927.     if (noCase && exact) {
  1928. Tcl_DStringInit(&patDString);
  1929. Tcl_DStringAppend(&patDString, pattern, -1);
  1930. Tcl_UtfToLower(Tcl_DStringValue(&patDString));
  1931. pattern = Tcl_DStringValue(&patDString);
  1932.     }
  1933.     Tcl_DStringInit(&line);
  1934.     if (TkTextGetIndex(interp, textPtr, argv[i+1], &index) != TCL_OK) {
  1935. code = TCL_ERROR;
  1936. goto done;
  1937.     }
  1938.     numLines = TkBTreeNumLines(textPtr->tree);
  1939.     startingLine = TkBTreeLineIndex(index.linePtr);
  1940.     startingByte = index.byteIndex;
  1941.     if (startingLine >= numLines) {
  1942. if (backwards) {
  1943.     startingLine = TkBTreeNumLines(textPtr->tree) - 1;
  1944.     startingByte = TkBTreeBytesInLine(TkBTreeFindLine(textPtr->tree,
  1945.     startingLine));
  1946. } else {
  1947.     startingLine = 0;
  1948.     startingByte = 0;
  1949. }
  1950.     }
  1951.     if (argsLeft == 1) {
  1952. if (TkTextGetIndex(interp, textPtr, argv[i+2], &stopIndex) != TCL_OK) {
  1953.     code = TCL_ERROR;
  1954.     goto done;
  1955. }
  1956. stopLine = TkBTreeLineIndex(stopIndex.linePtr);
  1957. if (!backwards && (stopLine == numLines)) {
  1958.     stopLine = numLines-1;
  1959. }
  1960. searchWholeText = 0;
  1961.     } else {
  1962. stopLine = 0;
  1963. searchWholeText = 1;
  1964.     }
  1965.     /*
  1966.      * Scan through all of the lines of the text circularly, starting
  1967.      * at the given index.
  1968.      */
  1969.     matchLength = patLength = 0; /* Only needed to prevent compiler
  1970.  * warnings. */
  1971.     if (exact) {
  1972. patLength = strlen(pattern);
  1973.     } else {
  1974. patObj = Tcl_NewStringObj(pattern, -1);
  1975. Tcl_IncrRefCount(patObj);
  1976. regexp = Tcl_GetRegExpFromObj(interp, patObj,
  1977. (noCase ? TCL_REG_NOCASE : 0) | TCL_REG_ADVANCED);
  1978. if (regexp == NULL) {
  1979.     code = TCL_ERROR;
  1980.     goto done;
  1981. }
  1982.     }
  1983.     lineNum = startingLine;
  1984.     code = TCL_OK;
  1985.     for (passes = 0; passes < 2; ) {
  1986. if (lineNum >= numLines) {
  1987.     /*
  1988.      * Don't search the dummy last line of the text.
  1989.      */
  1990.     goto nextLine;
  1991. }
  1992. /*
  1993.  * Extract the text from the line.  If we're doing regular
  1994.  * expression matching, drop the newline from the line, so
  1995.  * that "$" can be used to match the end of the line.
  1996.  */
  1997. linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
  1998. curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
  1999. for (segPtr = linePtr->segPtr; segPtr != NULL;
  2000. curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
  2001.     if ((segPtr->typePtr != &tkTextCharType)
  2002.     || (!searchElide && TkTextIsElided(textPtr, &curIndex))) {
  2003. continue;
  2004.     }
  2005.     Tcl_DStringAppend(&line, segPtr->body.chars, segPtr->size);
  2006. }
  2007. if (!exact) {
  2008.     Tcl_DStringSetLength(&line, Tcl_DStringLength(&line)-1);
  2009. }
  2010. startOfLine = Tcl_DStringValue(&line);
  2011. /*
  2012.  * If we're ignoring case, convert the line to lower case.
  2013.  */
  2014. if (noCase) {
  2015.     Tcl_DStringSetLength(&line,
  2016.     Tcl_UtfToLower(Tcl_DStringValue(&line)));
  2017. }
  2018. /*
  2019.  * Check for matches within the current line.  If so, and if we're
  2020.  * searching backwards, repeat the search to find the last match
  2021.  * in the line.  (Note: The lastByte should include the NULL char
  2022.  * so we can handle searching for end of line easier.)
  2023.  */
  2024. matchByte = -1;
  2025. firstByte = 0;
  2026. lastByte = Tcl_DStringLength(&line) + 1;
  2027. if (lineNum == startingLine) {
  2028.     int indexInDString;
  2029.     /*
  2030.      * The starting line is tricky: the first time we see it
  2031.      * we check one part of the line, and the second pass through
  2032.      * we check the other part of the line.  We have to be very
  2033.      * careful here because there could be embedded windows or
  2034.      * other things that are not in the extracted line.  Rescan
  2035.      * the original line to compute the index in it of the first
  2036.      * character.
  2037.      */
  2038.     indexInDString = startingByte;
  2039.     for (segPtr = linePtr->segPtr, leftToScan = startingByte;
  2040.     leftToScan > 0; segPtr = segPtr->nextPtr) {
  2041. if (segPtr->typePtr != &tkTextCharType) {
  2042.     indexInDString -= segPtr->size;
  2043. }
  2044. leftToScan -= segPtr->size;
  2045.     }
  2046.     passes++;
  2047.     if ((passes == 1) ^ backwards) {
  2048. /*
  2049.  * Only use the last part of the line.
  2050.  */
  2051. firstByte = indexInDString;
  2052. if ((firstByte >= Tcl_DStringLength(&line))
  2053. && !((Tcl_DStringLength(&line) == 0) && !exact)) {
  2054.     goto nextLine;
  2055. }
  2056.     } else {
  2057. /*
  2058.  * Use only the first part of the line.
  2059.  */
  2060. lastByte = indexInDString;
  2061.     }
  2062. }
  2063. do {
  2064.     int thisLength;
  2065.     Tcl_UniChar ch;
  2066.     if (exact) {
  2067. p = strstr(startOfLine + firstByte, /* INTL: Native. */
  2068. pattern); 
  2069. if (p == NULL) {
  2070.     break;
  2071. }
  2072. i = p - startOfLine;
  2073. thisLength = patLength;
  2074.     } else {
  2075. CONST char *start, *end;
  2076. int match;
  2077. match = Tcl_RegExpExec(interp, regexp,
  2078. startOfLine + firstByte, startOfLine);
  2079. if (match < 0) {
  2080.     code = TCL_ERROR;
  2081.     goto done;
  2082. }
  2083. if (!match) {
  2084.     break;
  2085. }
  2086. Tcl_RegExpRange(regexp, 0, &start, &end);
  2087. i = start - startOfLine;
  2088. thisLength = end - start;
  2089.     }
  2090.     if (i >= lastByte) {
  2091. break;
  2092.     }
  2093.     matchByte = i;
  2094.     matchLength = thisLength;
  2095.     firstByte = i + Tcl_UtfToUniChar(startOfLine + matchByte, &ch);
  2096. } while (backwards);
  2097. /*
  2098.  * If we found a match then we're done.  Make sure that
  2099.  * the match occurred before the stopping index, if one was
  2100.  * specified.
  2101.  */
  2102. if (matchByte >= 0) {
  2103.     int numChars;
  2104.     /*
  2105.      * Convert the byte length to a character count.
  2106.      */
  2107.     numChars = Tcl_NumUtfChars(startOfLine + matchByte,
  2108.     matchLength);
  2109.     /*
  2110.      * The index information returned by the regular expression
  2111.      * parser only considers textual information:  it doesn't
  2112.      * account for embedded windows, elided text (when we are not
  2113.      * searching elided text) or any other non-textual info.
  2114.      * Scan through the line's segments again to adjust both
  2115.      * matchChar and matchCount.
  2116.      *
  2117.      * We will walk through the segments of this line until we have
  2118.      * either reached the end of the match or we have reached the end
  2119.      * of the line.
  2120.      */
  2121.     curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
  2122.     for (segPtr = linePtr->segPtr, leftToScan = matchByte;
  2123.     leftToScan >= 0 && segPtr; segPtr = segPtr->nextPtr) {
  2124. if (segPtr->typePtr != &tkTextCharType || 
  2125. (!searchElide && TkTextIsElided(textPtr, &curIndex))) {
  2126.     matchByte += segPtr->size;
  2127. } else {
  2128.     leftToScan -= segPtr->size;
  2129. }
  2130. curIndex.byteIndex += segPtr->size;
  2131.     }
  2132.     for (leftToScan += matchLength; leftToScan > 0;
  2133.     segPtr = segPtr->nextPtr) {
  2134. if (segPtr->typePtr != &tkTextCharType) {
  2135.     numChars += segPtr->size;
  2136.     continue;
  2137. }
  2138. leftToScan -= segPtr->size;
  2139.     }
  2140.     TkTextMakeByteIndex(textPtr->tree, lineNum, matchByte, &index);
  2141.     if (!searchWholeText) {
  2142. if (!backwards && (TkTextIndexCmp(&index, &stopIndex) >= 0)) {
  2143.     goto done;
  2144. }
  2145. if (backwards && (TkTextIndexCmp(&index, &stopIndex) < 0)) {
  2146.     goto done;
  2147. }
  2148.     }
  2149.     if (varName != NULL) {
  2150. sprintf(buffer, "%d", numChars);
  2151. if (Tcl_SetVar(interp, varName, buffer, TCL_LEAVE_ERR_MSG)
  2152. == NULL) {
  2153.     code = TCL_ERROR;
  2154.     goto done;
  2155. }
  2156.     }
  2157.     TkTextPrintIndex(&index, buffer);
  2158.     Tcl_SetResult(interp, buffer, TCL_VOLATILE);
  2159.     goto done;
  2160. }
  2161. /*
  2162.  * Go to the next (or previous) line;
  2163.  */
  2164. nextLine:
  2165. if (backwards) {
  2166.     lineNum--;
  2167.     if (!searchWholeText) {
  2168. if (lineNum < stopLine) {
  2169.     break;
  2170. }
  2171.     } else if (lineNum < 0) {
  2172. lineNum = numLines-1;
  2173.     }
  2174. } else {
  2175.     lineNum++;
  2176.     if (!searchWholeText) {
  2177. if (lineNum > stopLine) {
  2178.     break;
  2179. }
  2180.     } else if (lineNum >= numLines) {
  2181. lineNum = 0;
  2182.     }
  2183. }
  2184. Tcl_DStringSetLength(&line, 0);
  2185.     }
  2186.     done:
  2187.     Tcl_DStringFree(&line);
  2188.     if (noCase && exact) {
  2189. Tcl_DStringFree(&patDString);
  2190.     }
  2191.     if (patObj != NULL) {
  2192. Tcl_DecrRefCount(patObj);
  2193.     }
  2194.     return code;
  2195. }
  2196. /*
  2197.  *----------------------------------------------------------------------
  2198.  *
  2199.  * TkTextGetTabs --
  2200.  *
  2201.  * Parses a string description of a set of tab stops.
  2202.  *
  2203.  * Results:
  2204.  * The return value is a pointer to a malloc'ed structure holding
  2205.  * parsed information about the tab stops.  If an error occurred
  2206.  * then the return value is NULL and an error message is left in
  2207.  * the interp's result.
  2208.  *
  2209.  * Side effects:
  2210.  * Memory is allocated for the structure that is returned.  It is
  2211.  * up to the caller to free this structure when it is no longer
  2212.  * needed.
  2213.  *
  2214.  *----------------------------------------------------------------------
  2215.  */
  2216. TkTextTabArray *
  2217. TkTextGetTabs(interp, tkwin, string)
  2218.     Tcl_Interp *interp; /* Used for error reporting. */
  2219.     Tk_Window tkwin; /* Window in which the tabs will be
  2220.  * used. */
  2221.     char *string; /* Description of the tab stops.  See
  2222.  * the text manual entry for details. */
  2223. {
  2224.     int argc, i, count, c;
  2225.     CONST char **argv;
  2226.     TkTextTabArray *tabArrayPtr;
  2227.     TkTextTab *tabPtr;
  2228.     Tcl_UniChar ch;
  2229.     if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
  2230. return NULL;
  2231.     }
  2232.     /*
  2233.      * First find out how many entries we need to allocate in the
  2234.      * tab array.
  2235.      */
  2236.     count = 0;
  2237.     for (i = 0; i < argc; i++) {
  2238. c = argv[i][0];
  2239. if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
  2240.     count++;
  2241. }
  2242.     }
  2243.     /*
  2244.      * Parse the elements of the list one at a time to fill in the
  2245.      * array.
  2246.      */
  2247.     tabArrayPtr = (TkTextTabArray *) ckalloc((unsigned)
  2248.     (sizeof(TkTextTabArray) + (count-1)*sizeof(TkTextTab)));
  2249.     tabArrayPtr->numTabs = 0;
  2250.     for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i  < argc; i++, tabPtr++) {
  2251. if (Tk_GetPixels(interp, tkwin, argv[i], &tabPtr->location)
  2252. != TCL_OK) {
  2253.     goto error;
  2254. }
  2255. tabArrayPtr->numTabs++;
  2256. /*
  2257.  * See if there is an explicit alignment in the next list
  2258.  * element.  Otherwise just use "left".
  2259.  */
  2260. tabPtr->alignment = LEFT;
  2261. if ((i+1) == argc) {
  2262.     continue;
  2263. }
  2264. Tcl_UtfToUniChar(argv[i+1], &ch);
  2265. if (!Tcl_UniCharIsAlpha(ch)) {
  2266.     continue;
  2267. }
  2268. i += 1;
  2269. c = argv[i][0];
  2270. if ((c == 'l') && (strncmp(argv[i], "left",
  2271. strlen(argv[i])) == 0)) {
  2272.     tabPtr->alignment = LEFT;
  2273. } else if ((c == 'r') && (strncmp(argv[i], "right",
  2274. strlen(argv[i])) == 0)) {
  2275.     tabPtr->alignment = RIGHT;
  2276. } else if ((c == 'c') && (strncmp(argv[i], "center",
  2277. strlen(argv[i])) == 0)) {
  2278.     tabPtr->alignment = CENTER;
  2279. } else if ((c == 'n') && (strncmp(argv[i],
  2280. "numeric", strlen(argv[i])) == 0)) {
  2281.     tabPtr->alignment = NUMERIC;
  2282. } else {
  2283.     Tcl_AppendResult(interp, "bad tab alignment "",
  2284.     argv[i], "": must be left, right, center, or numeric",
  2285.     (char *) NULL);
  2286.     goto error;
  2287. }
  2288.     }
  2289.     ckfree((char *) argv);
  2290.     return tabArrayPtr;
  2291.     error:
  2292.     ckfree((char *) tabArrayPtr);
  2293.     ckfree((char *) argv);
  2294.     return NULL;
  2295. }
  2296. /*
  2297.  *----------------------------------------------------------------------
  2298.  *
  2299.  * TextDumpCmd --
  2300.  *
  2301.  * Return information about the text, tags, marks, and embedded windows
  2302.  * and images in a text widget.  See the man page for the description
  2303.  * of the text dump operation for all the details.
  2304.  *
  2305.  * Results:
  2306.  * A standard Tcl result.
  2307.  *
  2308.  * Side effects:
  2309.  * Memory is allocated for the result, if needed (standard Tcl result
  2310.  * side effects).
  2311.  *
  2312.  *----------------------------------------------------------------------
  2313.  */
  2314. static int
  2315. TextDumpCmd(textPtr, interp, argc, argv)
  2316.     register TkText *textPtr; /* Information about text widget. */
  2317.     Tcl_Interp *interp; /* Current interpreter. */
  2318.     int argc; /* Number of arguments. */
  2319.     CONST char **argv; /* Argument strings.  Someone else has already
  2320.  * parsed this command enough to know that
  2321.  * argv[1] is "dump". */
  2322. {
  2323.     TkTextIndex index1, index2;
  2324.     int arg;
  2325.     int lineno; /* Current line number */
  2326.     int what = 0; /* bitfield to select segment types */
  2327.     int atEnd; /* True if dumping up to logical end */
  2328.     TkTextLine *linePtr;
  2329.     CONST char *command = NULL; /* Script callback to apply to segments */
  2330. #define TK_DUMP_TEXT 0x1
  2331. #define TK_DUMP_MARK 0x2
  2332. #define TK_DUMP_TAG 0x4
  2333. #define TK_DUMP_WIN 0x8
  2334. #define TK_DUMP_IMG 0x10
  2335. #define TK_DUMP_ALL (TK_DUMP_TEXT|TK_DUMP_MARK|TK_DUMP_TAG| 
  2336. TK_DUMP_WIN|TK_DUMP_IMG)
  2337.     for (arg=2 ; argv[arg] != (char *) NULL ; arg++) {
  2338. size_t len;
  2339. if (argv[arg][0] != '-') {
  2340.     break;
  2341. }
  2342. len = strlen(argv[arg]);
  2343. if (strncmp("-all", argv[arg], len) == 0) {
  2344.     what = TK_DUMP_ALL;
  2345. } else if (strncmp("-text", argv[arg], len) == 0) {
  2346.     what |= TK_DUMP_TEXT;
  2347. } else if (strncmp("-tag", argv[arg], len) == 0) {
  2348.     what |= TK_DUMP_TAG;
  2349. } else if (strncmp("-mark", argv[arg], len) == 0) {
  2350.     what |= TK_DUMP_MARK;
  2351. } else if (strncmp("-image", argv[arg], len) == 0) {
  2352.     what |= TK_DUMP_IMG;
  2353. } else if (strncmp("-window", argv[arg], len) == 0) {
  2354.     what |= TK_DUMP_WIN;
  2355. } else if (strncmp("-command", argv[arg], len) == 0) {
  2356.     arg++;
  2357.     if (arg >= argc) {
  2358. Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL);
  2359. return TCL_ERROR;
  2360.     }
  2361.     command = argv[arg];
  2362. } else {
  2363.     Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL);
  2364.     return TCL_ERROR;
  2365. }
  2366.     }
  2367.     if (arg >= argc) {
  2368. Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL);
  2369. return TCL_ERROR;
  2370.     }
  2371.     if (what == 0) {
  2372. what = TK_DUMP_ALL;
  2373.     }
  2374.     if (TkTextGetIndex(interp, textPtr, argv[arg], &index1) != TCL_OK) {
  2375. return TCL_ERROR;
  2376.     }
  2377.     lineno = TkBTreeLineIndex(index1.linePtr);
  2378.     arg++;
  2379.     atEnd = 0;
  2380.     if (argc == arg) {
  2381. TkTextIndexForwChars(&index1, 1, &index2);
  2382.     } else {
  2383. if (TkTextGetIndex(interp, textPtr, argv[arg], &index2) != TCL_OK) {
  2384.     return TCL_ERROR;
  2385. }
  2386. if (strncmp(argv[arg], "end", strlen(argv[arg])) == 0) {
  2387.     atEnd = 1;
  2388. }
  2389.     }
  2390.     if (TkTextIndexCmp(&index1, &index2) >= 0) {
  2391. return TCL_OK;
  2392.     }
  2393.     if (index1.linePtr == index2.linePtr) {
  2394. DumpLine(interp, textPtr, what, index1.linePtr,
  2395.     index1.byteIndex, index2.byteIndex, lineno, command);
  2396.     } else {
  2397. DumpLine(interp, textPtr, what, index1.linePtr,
  2398. index1.byteIndex, 32000000, lineno, command);
  2399. linePtr = index1.linePtr;
  2400. while ((linePtr = TkBTreeNextLine(linePtr)) != (TkTextLine *)NULL) {
  2401.     lineno++;
  2402.     if (linePtr == index2.linePtr) {
  2403. break;
  2404.     }
  2405.     DumpLine(interp, textPtr, what, linePtr, 0, 32000000,
  2406.     lineno, command);
  2407. }
  2408. DumpLine(interp, textPtr, what, index2.linePtr, 0,
  2409. index2.byteIndex, lineno, command);
  2410.     }
  2411.     /*
  2412.      * Special case to get the leftovers hiding at the end mark.
  2413.      */
  2414.     if (atEnd) {
  2415. DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr,
  2416. 0, 1, lineno, command);     
  2417.     }
  2418.     return TCL_OK;
  2419. }
  2420. /*
  2421.  * DumpLine
  2422.  *  Return information about a given text line from character
  2423.  * position "start" up to, but not including, "end".
  2424.  *
  2425.  * Results:
  2426.  * A standard Tcl result.
  2427.  *
  2428.  * Side effects:
  2429.  * None, but see DumpSegment.
  2430.  */
  2431. static void
  2432. DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command)
  2433.     Tcl_Interp *interp;
  2434.     TkText *textPtr;
  2435.     int what; /* bit flags to select segment types */
  2436.     TkTextLine *linePtr; /* The current line */
  2437.     int startByte, endByte; /* Byte range to dump */
  2438.     int lineno; /* Line number for indices dump */
  2439.     CONST char *command; /* Script to apply to the segment */
  2440. {
  2441.     int offset;
  2442.     TkTextSegment *segPtr, *nextPtr;
  2443.     TkTextIndex index;
  2444.     /*
  2445.      * Must loop through line looking at its segments.
  2446.      * character
  2447.      * toggleOn, toggleOff
  2448.      * mark
  2449.      * image
  2450.      * window
  2451.      */
  2452.     for (offset = 0, segPtr = linePtr->segPtr ;
  2453.     (offset < endByte) && (segPtr != (TkTextSegment *)NULL) ;
  2454.     offset += segPtr->size, segPtr = nextPtr) {
  2455. nextPtr = segPtr->nextPtr;
  2456. if ((what & TK_DUMP_TEXT) && (segPtr->typePtr == &tkTextCharType) &&
  2457. (offset + segPtr->size > startByte)) {
  2458.     char savedChar; /* Last char used in the seg */
  2459.     int last = segPtr->size; /* Index of savedChar */
  2460.     int first = 0; /* Index of first char in seg */
  2461.     if (offset + segPtr->size > endByte) {
  2462. last = endByte - offset;
  2463.     }
  2464.     if (startByte > offset) {
  2465. first = startByte - offset;
  2466.     }
  2467.     savedChar = segPtr->body.chars[last];
  2468.     segPtr->body.chars[last] = '';
  2469.     
  2470.     TkTextMakeByteIndex(textPtr->tree, lineno, offset + first, &index);
  2471.     DumpSegment(interp, "text", segPtr->body.chars + first,
  2472.     command, &index, what);
  2473.     segPtr->body.chars[last] = savedChar;
  2474. } else if ((offset >= startByte)) {
  2475.     if ((what & TK_DUMP_MARK) && (segPtr->typePtr->name[0] == 'm')) {
  2476. TkTextMark *markPtr = (TkTextMark *)&segPtr->body;
  2477. char *name = Tcl_GetHashKey(&textPtr->markTable, markPtr->hPtr);
  2478. TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index);
  2479. DumpSegment(interp, "mark", name, command, &index, what);
  2480.     } else if ((what & TK_DUMP_TAG) &&
  2481. (segPtr->typePtr == &tkTextToggleOnType)) {
  2482. TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index);
  2483. DumpSegment(interp, "tagon",
  2484. segPtr->body.toggle.tagPtr->name,
  2485. command, &index, what);
  2486.     } else if ((what & TK_DUMP_TAG) && 
  2487. (segPtr->typePtr == &tkTextToggleOffType)) {
  2488. TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index);
  2489. DumpSegment(interp, "tagoff",
  2490. segPtr->body.toggle.tagPtr->name,
  2491. command, &index, what);
  2492.     } else if ((what & TK_DUMP_IMG) && 
  2493. (segPtr->typePtr->name[0] == 'i')) {
  2494. TkTextEmbImage *eiPtr = (TkTextEmbImage *)&segPtr->body;
  2495. char *name = (eiPtr->name ==  NULL) ? "" : eiPtr->name;
  2496. TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index);
  2497. DumpSegment(interp, "image", name,
  2498. command, &index, what);
  2499.     } else if ((what & TK_DUMP_WIN) && 
  2500. (segPtr->typePtr->name[0] == 'w')) {
  2501. TkTextEmbWindow *ewPtr = (TkTextEmbWindow *)&segPtr->body;
  2502. char *pathname;
  2503. if (ewPtr->tkwin == (Tk_Window) NULL) {
  2504.     pathname = "";
  2505. } else {
  2506.     pathname = Tk_PathName(ewPtr->tkwin);
  2507. }
  2508. TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index);
  2509. DumpSegment(interp, "window", pathname,
  2510. command, &index, what);
  2511.     }
  2512. }
  2513. if (nextPtr != segPtr->nextPtr) {
  2514.     /*
  2515.      * Someone modified the text widget while we were dumping.
  2516.      * Just stop dumping. [Bug 1414171]
  2517.      */
  2518.     break;
  2519. }
  2520.     }
  2521. }
  2522. /*
  2523.  * DumpSegment
  2524.  * Either append information about the current segment to the result,
  2525.  * or make a script callback with that information as arguments.
  2526.  *
  2527.  * Results:
  2528.  * None
  2529.  *
  2530.  * Side effects:
  2531.  * Either evals the callback or appends elements to the result string.
  2532.  */
  2533. static int
  2534. DumpSegment(interp, key, value, command, index, what)
  2535.     Tcl_Interp *interp;
  2536.     char *key; /* Segment type key */
  2537.     char *value; /* Segment value */
  2538.     CONST char *command; /* Script callback */
  2539.     TkTextIndex *index;         /* index with line/byte position info */
  2540.     int what; /* Look for TK_DUMP_INDEX bit */
  2541. {
  2542.     char buffer[TCL_INTEGER_SPACE*2];
  2543.     TkTextPrintIndex(index, buffer);
  2544.     if (command == NULL) {
  2545. Tcl_AppendElement(interp, key);
  2546. Tcl_AppendElement(interp, value);
  2547. Tcl_AppendElement(interp, buffer);
  2548. return TCL_OK;
  2549.     } else {
  2550. CONST char *argv[4];
  2551. char *list;
  2552. int result;
  2553. argv[0] = key;
  2554. argv[1] = value;
  2555. argv[2] = buffer;
  2556. argv[3] = NULL;
  2557. list = Tcl_Merge(3, argv);
  2558. result = Tcl_VarEval(interp, command, " ", list, (char *) NULL);
  2559. ckfree(list);
  2560. return result;
  2561.     }
  2562. }
  2563. /*
  2564.  * TextEditUndo --
  2565.  *    undo the last change.
  2566.  *
  2567.  * Results:
  2568.  *    None
  2569.  *
  2570.  * Side effects:
  2571.  *    None.
  2572.  */
  2573. static int
  2574. TextEditUndo(textPtr)
  2575.     TkText     * textPtr;          /* Overall information about text widget. */
  2576. {
  2577.     int status;
  2578.     if (!textPtr->undo) {
  2579.        return TCL_OK;
  2580.     }
  2581.     /* Turn off the undo feature */
  2582.     textPtr->undo = 0;
  2583.     /* The dirty counter should count downwards as we are undoing things */
  2584.     textPtr->isDirtyIncrement = -1;
  2585.     /* revert one compound action */
  2586.     status = TkUndoRevert(textPtr->undoStack);
  2587.     /* Restore the isdirty increment */
  2588.     textPtr->isDirtyIncrement = 1;
  2589.     /* Turn back on the undo feature */
  2590.     textPtr->undo = 1;
  2591.     return status;
  2592. }
  2593. /*
  2594.  * TextEditRedo --
  2595.  *    redo the last undone change.
  2596.  *
  2597.  * Results:
  2598.  *    None
  2599.  *
  2600.  * Side effects:
  2601.  *    None.
  2602.  */
  2603. static int
  2604. TextEditRedo(textPtr)
  2605.     TkText     * textPtr;       /* Overall information about text widget. */
  2606. {
  2607.     int status;
  2608.     if (!textPtr->undo) {
  2609.        return TCL_OK;
  2610.     }
  2611.     /* Turn off the undo feature temporarily */
  2612.     textPtr->undo = 0;
  2613.     /* reapply one compound action */
  2614.     status = TkUndoApply(textPtr->undoStack);
  2615.     /* Turn back on the undo feature */
  2616.     textPtr->undo = 1;
  2617.     return status;
  2618. }
  2619. /*
  2620.  * TextEditCmd --
  2621.  *
  2622.  *    Handle the subcommands to "$text edit ...".
  2623.  *    See documentation for details.
  2624.  *
  2625.  * Results:
  2626.  *    None
  2627.  *
  2628.  * Side effects:
  2629.  *    None.
  2630.  */
  2631. static int
  2632. TextEditCmd(textPtr, interp, argc, argv)
  2633.     TkText *textPtr;          /* Information about text widget. */
  2634.     Tcl_Interp *interp;       /* Current interpreter. */
  2635.     int argc;                 /* Number of arguments. */
  2636.     CONST char **argv;        /* Argument strings. */
  2637. {
  2638.     int      c;
  2639.     size_t   length;
  2640.     if (argc < 3) {
  2641. Tcl_AppendResult(interp, "wrong # args: should be "",
  2642. argv[0], " edit option ?arg arg ...?"", (char *) NULL);
  2643. return TCL_ERROR;
  2644.     }
  2645.     c = argv[2][0];
  2646.     length = strlen(argv[2]);
  2647.     if ((c == 'm') && (strncmp(argv[2], "modified", length) == 0)) {
  2648. if (argc == 3) {
  2649.     Tcl_SetObjResult(interp, Tcl_NewBooleanObj(textPtr->isDirty));
  2650. } else if (argc != 4) {
  2651.     Tcl_AppendResult(interp, "wrong # args: should be "",
  2652.     argv[0], " edit modified ?boolean?"", (char *) NULL);
  2653.     return TCL_ERROR;
  2654. } else {
  2655.     int setModified;
  2656.     if (Tcl_GetBoolean(interp, argv[3], &setModified) != TCL_OK) {
  2657. return TCL_ERROR;
  2658.     }
  2659.     /*
  2660.      * Set or reset the dirty info, but trigger a Modified event only
  2661.      * if it has changed.  Ensure a rationalized value for the bit.
  2662.      */
  2663.     setModified = setModified ? 1 : 0;
  2664.     textPtr->isDirty = setModified;
  2665.     if (textPtr->modifiedSet != setModified) {
  2666. textPtr->modifiedSet = setModified;
  2667. GenerateModifiedEvent(textPtr);
  2668.     }
  2669.         }
  2670.     } else if ((c == 'r') && (strncmp(argv[2], "redo", length) == 0)
  2671.     && (length >= 3)) {
  2672. if (argc != 3) {
  2673.     Tcl_AppendResult(interp, "wrong # args: should be "",
  2674.     argv[0], " edit redo"", (char *) NULL);
  2675.     return TCL_ERROR;
  2676. }
  2677.         if ( TextEditRedo(textPtr) ) {
  2678.             Tcl_AppendResult(interp, "nothing to redo", (char *) NULL);
  2679.     return TCL_ERROR;
  2680.         }
  2681.     } else if ((c == 'r') && (strncmp(argv[2], "reset", length) == 0)
  2682.     && (length >= 3)) {
  2683. if (argc != 3) {
  2684.     Tcl_AppendResult(interp, "wrong # args: should be "",
  2685.     argv[0], " edit reset"", (char *) NULL);
  2686.     return TCL_ERROR;
  2687. }
  2688.         TkUndoClearStacks(textPtr->undoStack);
  2689.     } else if ((c == 's') && (strncmp(argv[2], "separator", length) == 0)) {
  2690. if (argc != 3) {
  2691.     Tcl_AppendResult(interp, "wrong # args: should be "",
  2692.     argv[0], " edit separator"", (char *) NULL);
  2693.     return TCL_ERROR;
  2694. }
  2695.         TkUndoInsertUndoSeparator(textPtr->undoStack);
  2696.     } else if ((c == 'u') && (strncmp(argv[2], "undo", length) == 0)) {
  2697. if (argc != 3) {
  2698.     Tcl_AppendResult(interp, "wrong # args: should be "",
  2699.     argv[0], " edit undo"", (char *) NULL);
  2700.     return TCL_ERROR;
  2701. }
  2702.         if ( TextEditUndo(textPtr) ) {
  2703.             Tcl_AppendResult(interp, "nothing to undo",
  2704.     (char *) NULL);
  2705.     return TCL_ERROR;
  2706.         }
  2707.     } else {
  2708. Tcl_AppendResult(interp, "bad edit option "", argv[2],
  2709. "": must be modified, redo, reset, separator or undo",
  2710. (char *) NULL);
  2711. return TCL_ERROR;
  2712.     }
  2713.     
  2714.     return TCL_OK;
  2715. }
  2716. /*
  2717.  * TextGetText --
  2718.  *    Returns the text from indexPtr1 to indexPtr2, placing that text
  2719.  *    in the Tcl_DString given.  That DString should be free or uninitialized.
  2720.  *
  2721.  * Results:
  2722.  *    None.
  2723.  *
  2724.  * Side effects:
  2725.  *    Memory will be allocated for the DString.  Remember to free it.
  2726.  */
  2727. static void 
  2728. TextGetText(indexPtr1,indexPtr2, dsPtr)
  2729.     TkTextIndex *indexPtr1;
  2730.     TkTextIndex *indexPtr2;
  2731.     Tcl_DString *dsPtr;
  2732. {
  2733.     TkTextIndex tmpIndex;
  2734.     Tcl_DStringInit(dsPtr);
  2735.     
  2736.     TkTextMakeByteIndex(indexPtr1->tree, TkBTreeLineIndex(indexPtr1->linePtr),
  2737.     indexPtr1->byteIndex, &tmpIndex);
  2738.     if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) {
  2739. while (1) {
  2740.     int offset, last;
  2741.     TkTextSegment *segPtr;
  2742.     segPtr = TkTextIndexToSeg(&tmpIndex, &offset);
  2743.     last = segPtr->size;
  2744.     if (tmpIndex.linePtr == indexPtr2->linePtr) {
  2745. int last2;
  2746. if (indexPtr2->byteIndex == tmpIndex.byteIndex) {
  2747.     break;
  2748. }
  2749. last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset;
  2750. if (last2 < last) {
  2751.     last = last2;
  2752. }
  2753.     }
  2754.     if (segPtr->typePtr == &tkTextCharType) {
  2755. Tcl_DStringAppend(dsPtr, segPtr->body.chars + offset,
  2756. last - offset);
  2757.     }
  2758.     TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex);
  2759. }
  2760.     }
  2761. }
  2762. /*
  2763.  * GenerateModifiedEvent --
  2764.  *
  2765.  * Send an event that the text was modified. This is equivalent to
  2766.  *    event generate $textWidget <<Modified>>
  2767.  *
  2768.  * Results:
  2769.  * None
  2770.  *
  2771.  * Side effects:
  2772.  * May force the text window into existence.
  2773.  */
  2774. static void
  2775. GenerateModifiedEvent(
  2776.     TkText *textPtr) /* Information about text widget. */
  2777. {
  2778.     XEvent event;
  2779.     Tk_MakeWindowExist(textPtr->tkwin);
  2780.     memset(&event, 0, sizeof(event));
  2781.     event.xany.type = VirtualEvent;
  2782.     event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
  2783.     event.xany.send_event = False;
  2784.     event.xany.window = Tk_WindowId(textPtr->tkwin);
  2785.     event.xany.display = Tk_Display(textPtr->tkwin);
  2786.     ((XVirtualEvent *) &event)->name = Tk_GetUid("Modified");
  2787.     Tk_HandleEvent(&event);
  2788. }
  2789. /*
  2790.  * updateDirtyFlag --
  2791.  *    increases the dirtyness of the text widget
  2792.  *
  2793.  * Results:
  2794.  *    None
  2795.  *
  2796.  * Side effects:
  2797.  *    None.
  2798.  */
  2799. static void updateDirtyFlag (textPtr)
  2800.     TkText *textPtr;          /* Information about text widget. */
  2801. {
  2802.     int oldDirtyFlag;
  2803.     if (textPtr->modifiedSet) {
  2804.         return;
  2805.     }
  2806.     oldDirtyFlag = textPtr->isDirty;
  2807.     textPtr->isDirty += textPtr->isDirtyIncrement;
  2808.     if (textPtr->isDirty == 0 || oldDirtyFlag == 0) {
  2809. GenerateModifiedEvent(textPtr);
  2810.     }
  2811. }