]> www.wagner.pp.ru Git - oss/ck.git/blob - ckText.c
Ck console graphics toolkit
[oss/ck.git] / ckText.c
1 /* 
2  * ckText.c --
3  *
4  *      This module provides a big chunk of the implementation of
5  *      multi-line editable text widgets for ck.  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-1995 Sun Microsystems, Inc.
12  * Copyright (c) 1995 Christian Werner
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
18 #include "ckPort.h"
19 #include "ck.h"
20 #include "ckText.h"
21 #include "default.h"
22
23 /*
24  * Information used to parse text configuration options:
25  */
26
27 static Ck_ConfigSpec configSpecs[] = {
28     {CK_CONFIG_ATTR, "-attributes", "attributes", "Attributes",
29         DEF_TEXT_ATTR, Ck_Offset(CkText, attr), 0},
30     {CK_CONFIG_COLOR, "-background", "background", "Background",
31         DEF_TEXT_BG_COLOR, Ck_Offset(CkText, bg), CK_CONFIG_COLOR_ONLY},
32     {CK_CONFIG_COLOR, "-background", "background", "Background",
33         DEF_TEXT_BG_MONO, Ck_Offset(CkText, bg), CK_CONFIG_MONO_ONLY},
34     {CK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
35         (char *) NULL, 0, 0},
36     {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
37         (char *) NULL, 0, 0},
38     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
39         DEF_TEXT_FG, Ck_Offset(CkText, fg), 0},
40     {CK_CONFIG_COORD, "-height", "height", "Height",
41         DEF_TEXT_HEIGHT, Ck_Offset(CkText, height), 0},
42     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
43         "SelectAttributes", DEF_TEXT_SELECT_ATTR_COLOR,
44         Ck_Offset(CkText, selAttr), CK_CONFIG_COLOR_ONLY},
45     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
46         "SelectAttributes", DEF_TEXT_SELECT_ATTR_MONO,
47         Ck_Offset(CkText, selAttr), CK_CONFIG_MONO_ONLY},
48     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
49         DEF_TEXT_SELECT_BG_COLOR, Ck_Offset(CkText, selBg),
50         CK_CONFIG_COLOR_ONLY},
51     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
52         DEF_TEXT_SELECT_BG_MONO, Ck_Offset(CkText, selBg),
53         CK_CONFIG_MONO_ONLY},
54     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
55         DEF_TEXT_SELECT_FG_COLOR, Ck_Offset(CkText, selFg),
56         CK_CONFIG_COLOR_ONLY},
57     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
58         DEF_TEXT_SELECT_FG_MONO, Ck_Offset(CkText, selFg),
59         CK_CONFIG_MONO_ONLY},
60     {CK_CONFIG_UID, "-state", "state", "State",
61         DEF_TEXT_STATE, Ck_Offset(CkText, state), 0},
62     {CK_CONFIG_STRING, "-tabs", "tabs", "Tabs",
63         DEF_TEXT_TABS, Ck_Offset(CkText, tabOptionString), CK_CONFIG_NULL_OK},
64     {CK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
65         DEF_TEXT_TAKE_FOCUS, Ck_Offset(CkText, takeFocus),
66         CK_CONFIG_NULL_OK},
67     {CK_CONFIG_COORD, "-width", "width", "Width",
68         DEF_TEXT_WIDTH, Ck_Offset(CkText, width), 0},
69     {CK_CONFIG_UID, "-wrap", "wrap", "Wrap",
70         DEF_TEXT_WRAP, Ck_Offset(CkText, wrapMode), 0},
71     {CK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
72         DEF_TEXT_XSCROLL_COMMAND, Ck_Offset(CkText, xScrollCmd),
73         CK_CONFIG_NULL_OK},
74     {CK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
75         DEF_TEXT_YSCROLL_COMMAND, Ck_Offset(CkText, yScrollCmd),
76         CK_CONFIG_NULL_OK},
77     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
78         (char *) NULL, 0, 0}
79 };
80
81 /*
82  * Ck_Uid's used to represent text states:
83  */
84
85 Ck_Uid ckTextCharUid = NULL;
86 Ck_Uid ckTextDisabledUid = NULL;
87 Ck_Uid ckTextNoneUid = NULL;
88 Ck_Uid ckTextNormalUid = NULL;
89 Ck_Uid ckTextWordUid = NULL;
90
91 /*
92  * Boolean variable indicating whether or not special debugging code
93  * should be executed.
94  */
95
96 int ckTextDebug = 0;
97
98 /*
99  * Forward declarations for procedures defined later in this file:
100  */
101
102 static int              ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
103                             CkText *textPtr, int argc, char **argv, int flags));
104 static int              DeleteChars _ANSI_ARGS_((CkText *textPtr,
105                             char *index1String, char *index2String));
106 static void             DestroyText _ANSI_ARGS_((ClientData clientData));
107 static void             InsertChars _ANSI_ARGS_((CkText *textPtr,
108                             CkTextIndex *indexPtr, char *string));
109 static void             TextCmdDeletedProc _ANSI_ARGS_((
110                             ClientData clientData));
111 static void             TextEventProc _ANSI_ARGS_((ClientData clientData,
112                             CkEvent *eventPtr));
113 static int              TextSearchCmd _ANSI_ARGS_((CkText *textPtr,
114                             Tcl_Interp *interp, int argc, char **argv));
115 static int              TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
116                             Tcl_Interp *interp, int argc, char **argv));
117 \f
118 /*
119  *--------------------------------------------------------------
120  *
121  * Ck_TextCmd --
122  *
123  *      This procedure is invoked to process the "text" Tcl command.
124  *      See the user documentation for details on what it does.
125  *
126  * Results:
127  *      A standard Tcl result.
128  *
129  * Side effects:
130  *      See the user documentation.
131  *
132  *--------------------------------------------------------------
133  */
134
135 int
136 Ck_TextCmd(clientData, interp, argc, argv)
137     ClientData clientData;      /* Main window associated with
138                                  * interpreter. */
139     Tcl_Interp *interp;         /* Current interpreter. */
140     int argc;                   /* Number of arguments. */
141     char **argv;                /* Argument strings. */
142 {
143     CkWindow *mainPtr = (CkWindow *) clientData;
144     CkWindow *new;
145     register CkText *textPtr;
146     CkTextIndex startIndex;
147
148     if (argc < 2) {
149         Tcl_AppendResult(interp, "wrong # args: should be \"",
150                 argv[0], " pathName ?options?\"", (char *) NULL);
151         return TCL_ERROR;
152     }
153
154     /*
155      * Perform once-only initialization:
156      */
157
158     if (ckTextNormalUid == NULL) {
159         ckTextCharUid = Ck_GetUid("char");
160         ckTextDisabledUid = Ck_GetUid("disabled");
161         ckTextNoneUid = Ck_GetUid("none");
162         ckTextNormalUid = Ck_GetUid("normal");
163         ckTextWordUid = Ck_GetUid("word");
164     }
165
166     /*
167      * Create the window.
168      */
169
170     new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 0);
171     if (new == NULL) {
172         return TCL_ERROR;
173     }
174
175     textPtr = (CkText *) ckalloc(sizeof(CkText));
176     textPtr->winPtr = new;
177     textPtr->interp = interp;
178     textPtr->widgetCmd = Tcl_CreateCommand(interp,
179         new->pathName, TextWidgetCmd, (ClientData) textPtr,
180         TextCmdDeletedProc);
181     textPtr->tree = CkBTreeCreate();
182     Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
183     textPtr->numTags = 0;
184     Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
185     Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS);
186     textPtr->state = ckTextNormalUid;
187     textPtr->bg = 0;
188     textPtr->fg = 0;
189     textPtr->attr = 0;
190     textPtr->tabOptionString = NULL;
191     textPtr->tabArrayPtr = NULL;
192     textPtr->wrapMode = ckTextCharUid;
193     textPtr->width = 0;
194     textPtr->height = 0;
195     textPtr->prevWidth = new->width;
196     textPtr->prevHeight = new->height;
197     CkTextCreateDInfo(textPtr);
198 #if CK_USE_UTF
199     CkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex);
200 #else
201     CkTextMakeIndex(textPtr->tree, 0, 0, &startIndex);
202 #endif
203     CkTextSetYView(textPtr, &startIndex, 0);
204     textPtr->selTagPtr = NULL;
205     textPtr->selBg = 0;
206     textPtr->selFg = 0;
207     textPtr->selAttr = 0;
208     textPtr->abortSelections = 0;
209     textPtr->insertMarkPtr = NULL;
210     textPtr->bindingTable = NULL;
211     textPtr->currentMarkPtr = NULL;
212     textPtr->pickEvent.type = -1;
213     textPtr->numCurTags = 0;
214     textPtr->curTagArrayPtr = NULL;
215     textPtr->takeFocus = NULL;
216     textPtr->xScrollCmd = NULL;
217     textPtr->yScrollCmd = NULL;
218     textPtr->flags = 0;
219
220     /*
221      * Create the "sel" tag and the "current" and "insert" marks.
222      */
223
224     textPtr->selTagPtr = CkTextCreateTag(textPtr, "sel");
225     textPtr->currentMarkPtr = CkTextSetMark(textPtr, "current", &startIndex);
226     textPtr->insertMarkPtr = CkTextSetMark(textPtr, "insert", &startIndex);
227
228     Ck_SetClass(new, "Text");
229     Ck_CreateEventHandler(textPtr->winPtr,
230             CK_EV_EXPOSE | CK_EV_DESTROY | CK_EV_MAP | CK_EV_FOCUSIN |
231             CK_EV_FOCUSOUT,
232             TextEventProc, (ClientData) textPtr);
233     Ck_CreateEventHandler(textPtr->winPtr, CK_EV_KEYPRESS,
234             CkTextBindProc, (ClientData) textPtr);
235     if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
236         Ck_DestroyWindow(textPtr->winPtr);
237         return TCL_ERROR;
238     }
239     interp->result = textPtr->winPtr->pathName;
240
241     return TCL_OK;
242 }
243 \f
244 /*
245  *--------------------------------------------------------------
246  *
247  * TextWidgetCmd --
248  *
249  *      This procedure is invoked to process the Tcl command
250  *      that corresponds to a text widget.  See the user
251  *      documentation for details on what it does.
252  *
253  * Results:
254  *      A standard Tcl result.
255  *
256  * Side effects:
257  *      See the user documentation.
258  *
259  *--------------------------------------------------------------
260  */
261
262 static int
263 TextWidgetCmd(clientData, interp, argc, argv)
264     ClientData clientData;      /* Information about text widget. */
265     Tcl_Interp *interp;         /* Current interpreter. */
266     int argc;                   /* Number of arguments. */
267     char **argv;                /* Argument strings. */
268 {
269     register CkText *textPtr = (CkText *) clientData;
270     int result = TCL_OK;
271     size_t length;
272     int c;
273     CkTextIndex index1, index2;
274
275     if (argc < 2) {
276         Tcl_AppendResult(interp, "wrong # args: should be \"",
277                 argv[0], " option ?arg arg ...?\"", (char *) NULL);
278         return TCL_ERROR;
279     }
280     Ck_Preserve((ClientData) textPtr);
281     c = argv[1][0];
282     length = strlen(argv[1]);
283     if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
284         int x, y, width, height;
285
286         if (argc != 3) {
287             Tcl_AppendResult(interp, "wrong # args: should be \"",
288                     argv[0], " bbox index\"", (char *) NULL);
289             result = TCL_ERROR;
290             goto done;
291         }
292         if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
293             result = TCL_ERROR;
294             goto done;
295         }
296         if (CkTextCharBbox(textPtr, &index1, &x, &y, &width, &height) == 0) {
297             sprintf(interp->result, "%d %d %d %d", x, y, width, height);
298         }
299     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
300             && (length >= 2)) {
301         if (argc != 3) {
302             Tcl_AppendResult(interp, "wrong # args: should be \"",
303                     argv[0], " cget option\"",
304                     (char *) NULL);
305             result = TCL_ERROR;
306             goto done;
307         }
308         result = Ck_ConfigureValue(interp, textPtr->winPtr, configSpecs,
309                 (char *) textPtr, argv[2], 0);
310     } else if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
311             && (length >= 3)) {
312         int relation, value;
313         char *p;
314
315         if (argc != 5) {
316             Tcl_AppendResult(interp, "wrong # args: should be \"",
317                     argv[0], " compare index1 op index2\"", (char *) NULL);
318             result = TCL_ERROR;
319             goto done;
320         }
321         if ((CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK)
322                 || (CkTextGetIndex(interp, textPtr, argv[4], &index2)
323                 != TCL_OK)) {
324             result = TCL_ERROR;
325             goto done;
326         }
327         relation = CkTextIndexCmp(&index1, &index2);
328         p = argv[3];
329         if (p[0] == '<') {
330                 value = (relation < 0);
331             if ((p[1] == '=') && (p[2] == 0)) {
332                 value = (relation <= 0);
333             } else if (p[1] != 0) {
334                 compareError:
335                 Tcl_AppendResult(interp, "bad comparison operator \"",
336                         argv[3], "\": must be <, <=, ==, >=, >, or !=",
337                         (char *) NULL);
338                 result = TCL_ERROR;
339                 goto done;
340             }
341         } else if (p[0] == '>') {
342                 value = (relation > 0);
343             if ((p[1] == '=') && (p[2] == 0)) {
344                 value = (relation >= 0);
345             } else if (p[1] != 0) {
346                 goto compareError;
347             }
348         } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
349             value = (relation == 0);
350         } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
351             value = (relation != 0);
352         } else {
353             goto compareError;
354         }
355         interp->result = (value) ? "1" : "0";
356     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
357             && (length >= 3)) {
358         if (argc == 2) {
359             result = Ck_ConfigureInfo(interp, textPtr->winPtr, configSpecs,
360                     (char *) textPtr, (char *) NULL, 0);
361         } else if (argc == 3) {
362             result = Ck_ConfigureInfo(interp, textPtr->winPtr, configSpecs,
363                     (char *) textPtr, argv[2], 0);
364         } else {
365             result = ConfigureText(interp, textPtr, argc-2, argv+2,
366                     CK_CONFIG_ARGV_ONLY);
367         }
368     } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
369             && (length >= 3)) {
370         if (argc > 3) {
371             Tcl_AppendResult(interp, "wrong # args: should be \"",
372                     argv[0], " debug boolean\"", (char *) NULL);
373             result = TCL_ERROR;
374             goto done;
375         }
376         if (argc == 2) {
377             interp->result = (ckBTreeDebug) ? "1" : "0";
378         } else {
379             if (Tcl_GetBoolean(interp, argv[2], &ckBTreeDebug) != TCL_OK) {
380                 result = TCL_ERROR;
381                 goto done;
382             }
383             ckTextDebug = ckBTreeDebug;
384         }
385     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
386             && (length >= 3)) {
387         if ((argc != 3) && (argc != 4)) {
388             Tcl_AppendResult(interp, "wrong # args: should be \"",
389                     argv[0], " delete index1 ?index2?\"", (char *) NULL);
390             result = TCL_ERROR;
391             goto done;
392         }
393         if (textPtr->state == ckTextNormalUid) {
394             result = DeleteChars(textPtr, argv[2],
395                     (argc == 4) ? argv[3] : (char *) NULL);
396         }
397     } else if ((c == 'd') && (strncmp(argv[1], "dlineinfo", length) == 0)
398             && (length >= 2)) {
399         int x, y, width, height, base;
400
401         if (argc != 3) {
402             Tcl_AppendResult(interp, "wrong # args: should be \"",
403                     argv[0], " dlineinfo index\"", (char *) NULL);
404             result = TCL_ERROR;
405             goto done;
406         }
407         if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
408             result = TCL_ERROR;
409             goto done;
410         }
411         if (CkTextDLineInfo(textPtr, &index1, &x, &y, &width, &height, &base)
412                 == 0) {
413             sprintf(interp->result, "%d %d %d %d %d", x, y, width,
414                     height, base);
415         }
416     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
417         if ((argc != 3) && (argc != 4)) {
418             Tcl_AppendResult(interp, "wrong # args: should be \"",
419                     argv[0], " get index1 ?index2?\"", (char *) NULL);
420             result = TCL_ERROR;
421             goto done;
422         }
423         if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
424             result = TCL_ERROR;
425             goto done;
426         }
427         if (argc == 3) {
428             index2 = index1;
429             CkTextIndexForwChars(&index2, 1, &index2);
430         } else if (CkTextGetIndex(interp, textPtr, argv[3], &index2)
431                 != TCL_OK) {
432             result = TCL_ERROR;
433             goto done;
434         }
435         if (CkTextIndexCmp(&index1, &index2) >= 0) {
436             goto done;
437         }
438         while (1) {
439             int offset, last, savedChar;
440             CkTextSegment *segPtr;
441
442             segPtr = CkTextIndexToSeg(&index1, &offset);
443             last = segPtr->size;
444             if (index1.linePtr == index2.linePtr) {
445                 int last2;
446
447                 if (index2.charIndex == index1.charIndex) {
448                     break;
449                 }
450                 last2 = index2.charIndex - index1.charIndex + offset;
451                 if (last2 < last) {
452                     last = last2;
453                 }
454             }
455             if (segPtr->typePtr == &ckTextCharType) {
456                 savedChar = segPtr->body.chars[last];
457                 segPtr->body.chars[last] = 0;
458                 Tcl_AppendResult(interp, segPtr->body.chars + offset,
459                         (char *) NULL);
460                 segPtr->body.chars[last] = savedChar;
461             }
462             CkTextIndexForwChars(&index1, last-offset, &index1);
463         }
464     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
465             && (length >= 3)) {
466         if (argc != 3) {
467             Tcl_AppendResult(interp, "wrong # args: should be \"",
468                     argv[0], " index index\"",
469                     (char *) NULL);
470             result = TCL_ERROR;
471             goto done;
472         }
473         if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
474             result = TCL_ERROR;
475             goto done;
476         }
477         CkTextPrintIndex(&index1, interp->result);
478     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
479             && (length >= 3)) {
480         int i, j, numTags;
481         char **tagNames;
482         CkTextTag **oldTagArrayPtr;
483
484         if (argc < 4) {
485             Tcl_AppendResult(interp, "wrong # args: should be \"",
486                     argv[0],
487                     " insert index chars ?tagList chars tagList ...?\"",
488                     (char *) NULL);
489             result = TCL_ERROR;
490             goto done;
491         }
492         if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
493             result = TCL_ERROR;
494             goto done;
495         }
496         if (textPtr->state == ckTextNormalUid) {
497             for (j = 3;  j < argc; j += 2) {
498                 InsertChars(textPtr, &index1, argv[j]);
499                 if (argc > (j+1)) {
500                     CkTextIndexForwChars(&index1, (int) strlen(argv[j]),
501                             &index2);
502                     oldTagArrayPtr = CkBTreeGetTags(&index1, &numTags);
503                     if (oldTagArrayPtr != NULL) {
504                         for (i = 0; i < numTags; i++) {
505                             CkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
506                         }
507                         ckfree((char *) oldTagArrayPtr);
508                     }
509                     if (Tcl_SplitList(interp, argv[j+1], &numTags, &tagNames)
510                             != TCL_OK) {
511                         result = TCL_ERROR;
512                         goto done;
513                     }
514                     for (i = 0; i < numTags; i++) {
515                         CkBTreeTag(&index1, &index2,
516                                 CkTextCreateTag(textPtr, tagNames[i]), 1);
517                     }
518                     ckfree((char *) tagNames);
519                     index1 = index2;
520                 }
521             }
522         }
523     } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
524         result = CkTextMarkCmd(textPtr, interp, argc, argv);
525     } else if ((c == 's') && (strcmp(argv[1], "search") == 0)
526             && (length >= 3)) {
527         result = TextSearchCmd(textPtr, interp, argc, argv);
528     } else if ((c == 's') && (strcmp(argv[1], "see") == 0) && (length >= 3)) {
529         result = CkTextSeeCmd(textPtr, interp, argc, argv);
530     } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
531         result = CkTextTagCmd(textPtr, interp, argc, argv);
532 #if 0
533     } else if ((c == 'w') && (strncmp(argv[1], "window", length) == 0)) {
534         result = CkTextWindowCmd(textPtr, interp, argc, argv);
535 #endif
536     } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
537         result = CkTextXviewCmd(textPtr, interp, argc, argv);
538     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)
539             && (length >= 2)) {
540         result = CkTextYviewCmd(textPtr, interp, argc, argv);
541     } else {
542         Tcl_AppendResult(interp, "bad option \"", argv[1],
543                 "\":  must be bbox, cget, compare, configure, debug, delete, ",
544                 "dlineinfo, get, index, insert, mark, scan, search, see, ",
545                 "tag, window, xview, or yview",
546                 (char *) NULL);
547         result = TCL_ERROR;
548     }
549
550     done:
551     Ck_Release((ClientData) textPtr);
552     return result;
553 }
554 \f
555 /*
556  *----------------------------------------------------------------------
557  *
558  * DestroyText --
559  *
560  *      This procedure is invoked by Ck_EventuallyFree or Ck_Release
561  *      to clean up the internal structure of a text at a safe time
562  *      (when no-one is using it anymore).
563  *
564  * Results:
565  *      None.
566  *
567  * Side effects:
568  *      Everything associated with the text is freed up.
569  *
570  *----------------------------------------------------------------------
571  */
572
573 static void
574 DestroyText(clientData)
575     ClientData clientData;      /* Info about text widget. */
576 {
577     register CkText *textPtr = (CkText *) clientData;
578     Tcl_HashSearch search;
579     Tcl_HashEntry *hPtr;
580     CkTextTag *tagPtr;
581
582     /*
583      * Free up all the stuff that requires special handling, then
584      * let Ck_FreeOptions handle all the standard option-related
585      * stuff.  Special note:  free up display-related information
586      * before deleting the B-tree, since display-related stuff
587      * may refer to stuff in the B-tree.
588      */
589
590     CkTextFreeDInfo(textPtr);
591     CkBTreeDestroy(textPtr->tree);
592     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
593             hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
594         tagPtr = (CkTextTag *) Tcl_GetHashValue(hPtr);
595         CkTextFreeTag(textPtr, tagPtr);
596     }
597     Tcl_DeleteHashTable(&textPtr->tagTable);
598     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
599             hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
600         ckfree((char *) Tcl_GetHashValue(hPtr));
601     }
602     Tcl_DeleteHashTable(&textPtr->markTable);
603     if (textPtr->tabArrayPtr != NULL) {
604         ckfree((char *) textPtr->tabArrayPtr);
605     }
606     if (textPtr->bindingTable != NULL) {
607         Ck_DeleteBindingTable(textPtr->bindingTable);
608     }
609
610     /*
611      * NOTE: do NOT free up selBorder, selBdString, or selFgColorPtr:
612      * they are duplicates of information in the "sel" tag, which was
613      * freed up as part of deleting the tags above.
614      */
615
616     Ck_FreeOptions(configSpecs, (char *) textPtr, 0);
617     ckfree((char *) textPtr);
618 }
619 \f
620 /*
621  *----------------------------------------------------------------------
622  *
623  * ConfigureText --
624  *
625  *      This procedure is called to process an argv/argc list, plus
626  *      the Ck option database, in order to configure (or
627  *      reconfigure) a text widget.
628  *
629  * Results:
630  *      The return value is a standard Tcl result.  If TCL_ERROR is
631  *      returned, then interp->result contains an error message.
632  *
633  * Side effects:
634  *      Configuration information, such as text string, colors, font,
635  *      etc. get set for textPtr;  old resources get freed, if there
636  *      were any.
637  *
638  *----------------------------------------------------------------------
639  */
640
641 static int
642 ConfigureText(interp, textPtr, argc, argv, flags)
643     Tcl_Interp *interp;         /* Used for error reporting. */
644     register CkText *textPtr;   /* Information about widget;  may or may
645                                  * not already have values for some fields. */
646     int argc;                   /* Number of valid entries in argv. */
647     char **argv;                /* Arguments. */
648     int flags;                  /* Flags to pass to Ck_ConfigureWidget. */
649 {
650     if (Ck_ConfigureWidget(interp, textPtr->winPtr, configSpecs,
651             argc, argv, (char *) textPtr, flags) != TCL_OK) {
652         return TCL_ERROR;
653     }
654
655     /*
656      * A few other options also need special processing, such as parsing
657      * the geometry and setting the background from a 3-D border.
658      */
659
660     if ((textPtr->state != ckTextNormalUid)
661             && (textPtr->state != ckTextDisabledUid)) {
662         Tcl_AppendResult(interp, "bad state value \"", textPtr->state,
663                 "\":  must be normal or disabled", (char *) NULL);
664         textPtr->state = ckTextNormalUid;
665         return TCL_ERROR;
666     }
667
668     if ((textPtr->wrapMode != ckTextCharUid)
669             && (textPtr->wrapMode != ckTextNoneUid)
670             && (textPtr->wrapMode != ckTextWordUid)) {
671         Tcl_AppendResult(interp, "bad wrap mode \"", textPtr->wrapMode,
672                 "\":  must be char, none, or word", (char *) NULL);
673         textPtr->wrapMode = ckTextCharUid;
674         return TCL_ERROR;
675     }
676
677     /*
678      * Parse tab stops.
679      */
680
681     if (textPtr->tabArrayPtr != NULL) {
682         ckfree((char *) textPtr->tabArrayPtr);
683         textPtr->tabArrayPtr = NULL;
684     }
685     if (textPtr->tabOptionString != NULL) {
686         textPtr->tabArrayPtr = CkTextGetTabs(interp, textPtr->winPtr,
687                 textPtr->tabOptionString);
688         if (textPtr->tabArrayPtr == NULL) {
689             Tcl_AddErrorInfo(interp,"\n    (while processing -tabs option)");
690             return TCL_ERROR;
691         }
692     }
693
694     /*
695      * Make sure that configuration options are properly mirrored
696      * between the widget record and the "sel" tags.  NOTE: we don't
697      * have to free up information during the mirroring;  old
698      * information was freed when it was replaced in the widget
699      * record.
700      */
701
702     textPtr->selTagPtr->bg = textPtr->selBg;
703     textPtr->selTagPtr->fg = textPtr->selFg;
704     textPtr->selTagPtr->attr = textPtr->selAttr;
705     textPtr->selTagPtr->affectsDisplay = 0;
706 #if 0
707 /* ??? */
708     if ((textPtr->selTagPtr->border != NULL)
709             || (textPtr->selTagPtr->bdString != NULL)
710             || (textPtr->selTagPtr->reliefString != NULL)
711             || (textPtr->selTagPtr->bgStipple != None)
712             || (textPtr->selTagPtr->fgColor != NULL)
713             || (textPtr->selTagPtr->fontPtr != None)
714             || (textPtr->selTagPtr->fgStipple != None)
715             || (textPtr->selTagPtr->justifyString != NULL)
716             || (textPtr->selTagPtr->lMargin1String != NULL)
717             || (textPtr->selTagPtr->lMargin2String != NULL)
718             || (textPtr->selTagPtr->offsetString != NULL)
719             || (textPtr->selTagPtr->overstrikeString != NULL)
720             || (textPtr->selTagPtr->rMarginString != NULL)
721             || (textPtr->selTagPtr->spacing1String != NULL)
722             || (textPtr->selTagPtr->spacing2String != NULL)
723             || (textPtr->selTagPtr->spacing3String != NULL)
724             || (textPtr->selTagPtr->tabString != NULL)
725             || (textPtr->selTagPtr->underlineString != NULL)
726             || (textPtr->selTagPtr->wrapMode != NULL)) {
727         textPtr->selTagPtr->affectsDisplay = 1;
728     }
729 #endif
730     CkTextRedrawTag(textPtr, (CkTextIndex *) NULL, (CkTextIndex *) NULL,
731             textPtr->selTagPtr, 1);
732
733     /*
734      * Register the desired geometry for the window, and arrange for
735      * the window to be redisplayed.
736      */
737
738     if (textPtr->width <= 0) {
739         textPtr->width = 1;
740     }
741     if (textPtr->height <= 0) {
742         textPtr->height = 1;
743     }
744     Ck_GeometryRequest(textPtr->winPtr, textPtr->width, textPtr->height);
745
746     CkTextRelayoutWindow(textPtr);
747     return TCL_OK;
748 }
749 \f
750 /*
751  *--------------------------------------------------------------
752  *
753  * TextEventProc --
754  *
755  *      This procedure is invoked by the Ck dispatcher on
756  *      structure changes to a text.  For texts with 3D
757  *      borders, this procedure is also invoked for exposures.
758  *
759  * Results:
760  *      None.
761  *
762  * Side effects:
763  *      When the window gets deleted, internal structures get
764  *      cleaned up.  When it gets exposed, it is redisplayed.
765  *
766  *--------------------------------------------------------------
767  */
768
769 static void
770 TextEventProc(clientData, eventPtr)
771     ClientData clientData;      /* Information about window. */
772     register CkEvent *eventPtr; /* Information about event. */
773 {
774     register CkText *textPtr = (CkText *) clientData;
775     CkWindow *winPtr = textPtr->winPtr;
776     CkTextIndex index, index2;
777
778     if (eventPtr->type == CK_EV_EXPOSE) {
779         if ((textPtr->prevWidth != winPtr->width)
780                 || (textPtr->prevHeight != winPtr->height)) {
781             CkTextRelayoutWindow(textPtr);
782             textPtr->prevWidth = winPtr->width;
783             textPtr->prevHeight = winPtr->height;
784         }
785         CkTextRedrawRegion(textPtr, 0, 0, winPtr->width, winPtr->height);
786     } else if (eventPtr->type == CK_EV_DESTROY) {
787         if (textPtr->winPtr != NULL) {
788             textPtr->winPtr = NULL;
789             Tcl_DeleteCommand(textPtr->interp,
790                     Tcl_GetCommandName(textPtr->interp,
791                     textPtr->widgetCmd));
792         }
793         Ck_EventuallyFree((ClientData) textPtr, (Ck_FreeProc *) DestroyText);
794     } else if ((eventPtr->type == CK_EV_FOCUSIN)
795         || (eventPtr->type == CK_EV_FOCUSOUT)) {
796         if (eventPtr->type == CK_EV_FOCUSIN) {
797             textPtr->flags |= GOT_FOCUS;
798         } else {
799             textPtr->flags &= ~GOT_FOCUS;
800         }
801         CkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
802         CkTextIndexForwChars(&index, 1, &index2);
803         CkTextChanged(textPtr, &index, &index2);
804         CkTextRedrawRegion(textPtr, 0, 0, winPtr->width,
805             winPtr->height);
806     }
807 }
808 \f
809 /*
810  *----------------------------------------------------------------------
811  *
812  * TextCmdDeletedProc --
813  *
814  *      This procedure is invoked when a widget command is deleted.  If
815  *      the widget isn't already in the process of being destroyed,
816  *      this command destroys it.
817  *
818  * Results:
819  *      None.
820  *
821  * Side effects:
822  *      The widget is destroyed.
823  *
824  *----------------------------------------------------------------------
825  */
826
827 static void
828 TextCmdDeletedProc(clientData)
829     ClientData clientData;      /* Pointer to widget record for widget. */
830 {
831     CkText *textPtr = (CkText *) clientData;
832     CkWindow *winPtr = textPtr->winPtr;
833
834     /*
835      * This procedure could be invoked either because the window was
836      * destroyed and the command was then deleted (in which case ckwin
837      * is NULL) or because the command was deleted, and then this procedure
838      * destroys the widget.
839      */
840
841     if (winPtr != NULL) {
842         textPtr->winPtr = NULL;
843         Ck_DestroyWindow(winPtr);
844     }
845 }
846 \f
847 /*
848  *----------------------------------------------------------------------
849  *
850  * InsertChars --
851  *
852  *      This procedure implements most of the functionality of the
853  *      "insert" widget command.
854  *
855  * Results:
856  *      None.
857  *
858  * Side effects:
859  *      The characters in "string" get added to the text just before
860  *      the character indicated by "indexPtr".
861  *
862  *----------------------------------------------------------------------
863  */
864
865 static void
866 InsertChars(textPtr, indexPtr, string)
867     CkText *textPtr;            /* Overall information about text widget. */
868     CkTextIndex *indexPtr;      /* Where to insert new characters.  May be
869                                  * modified and/or invalidated. */
870     char *string;               /* Null-terminated string containing new
871                                  * information to add to text. */
872 {
873     int lineIndex;
874
875     /*
876      * Don't allow insertions on the last (dummy) line of the text.
877      */
878
879     lineIndex = CkBTreeLineIndex(indexPtr->linePtr);
880     if (lineIndex == CkBTreeNumLines(textPtr->tree)) {
881         lineIndex--;
882 #if CK_USE_UTF
883         CkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
884 #else
885         CkTextMakeIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
886 #endif
887     }
888
889     /*
890      * Notify the display module that lines are about to change, then do
891      * the insertion.
892      */
893
894     CkTextChanged(textPtr, indexPtr, indexPtr);
895     CkBTreeInsertChars(indexPtr, string);
896
897     /*
898      * Invalidate any selection retrievals in progress.
899      */
900
901     textPtr->abortSelections = 1;
902 }
903 \f
904 /*
905  *----------------------------------------------------------------------
906  *
907  * DeleteChars --
908  *
909  *      This procedure implements most of the functionality of the
910  *      "delete" widget command.
911  *
912  * Results:
913  *      Returns a standard Tcl result, and leaves an error message
914  *      in textPtr->interp if there is an error.
915  *
916  * Side effects:
917  *      Characters get deleted from the text.
918  *
919  *----------------------------------------------------------------------
920  */
921
922 static int
923 DeleteChars(textPtr, index1String, index2String)
924     CkText *textPtr;            /* Overall information about text widget. */
925     char *index1String;         /* String describing location of first
926                                  * character to delete. */
927     char *index2String;         /* String describing location of last
928                                  * character to delete.  NULL means just
929                                  * delete the one character given by
930                                  * index1String. */
931 {
932     int line1, line2, line, charIndex, resetView;
933     CkTextIndex index1, index2;
934
935     /*
936      * Parse the starting and stopping indices.
937      */
938
939     if (CkTextGetIndex(textPtr->interp, textPtr, index1String, &index1)
940             != TCL_OK) {
941         return TCL_ERROR;
942     }
943     if (index2String != NULL) {
944         if (CkTextGetIndex(textPtr->interp, textPtr, index2String, &index2)
945                 != TCL_OK) {
946             return TCL_ERROR;
947         }
948     } else {
949         index2 = index1;
950         CkTextIndexForwChars(&index2, 1, &index2);
951     }
952
953     /*
954      * Make sure there's really something to delete.
955      */
956
957     if (CkTextIndexCmp(&index1, &index2) >= 0) {
958         return TCL_OK;
959     }
960
961     /*
962      * The code below is ugly, but it's needed to make sure there
963      * is always a dummy empty line at the end of the text.  If the
964      * final newline of the file (just before the dummy line) is being
965      * deleted, then back up index to just before the newline.  If
966      * there is a newline just before the first character being deleted,
967      * then back up the first index too, so that an even number of lines
968      * gets deleted.  Furthermore, remove any tags that are present on
969      * the newline that isn't going to be deleted after all (this simulates
970      * deleting the newline and then adding a "clean" one back again).
971      */
972
973     line1 = CkBTreeLineIndex(index1.linePtr);
974     line2 = CkBTreeLineIndex(index2.linePtr);
975     if (line2 == CkBTreeNumLines(textPtr->tree)) {
976         CkTextTag **arrayPtr;
977         int arraySize, i;
978         CkTextIndex oldIndex2;
979
980         oldIndex2 = index2;
981         CkTextIndexBackChars(&oldIndex2, 1, &index2);
982         line2--;
983         if ((index1.charIndex == 0) && (line1 != 0)) {
984             CkTextIndexBackChars(&index1, 1, &index1);
985             line1--;
986         }
987         arrayPtr = CkBTreeGetTags(&index2, &arraySize);
988         if (arrayPtr != NULL) {
989             for (i = 0; i < arraySize; i++) {
990                 CkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
991             }
992             ckfree((char *) arrayPtr);
993         }
994     }
995
996     /*
997      * Tell the display what's about to happen so it can discard
998      * obsolete display information, then do the deletion.  Also,
999      * if the deletion involves the top line on the screen, then
1000      * we have to reset the view (the deletion will invalidate
1001      * textPtr->topIndex).  Compute what the new first character
1002      * will be, then do the deletion, then reset the view.
1003      */
1004
1005     CkTextChanged(textPtr, &index1, &index2);
1006     resetView = line = charIndex = 0;
1007     if (CkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) {
1008         if (CkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) {
1009             /*
1010              * Deletion range straddles topIndex: use the beginning
1011              * of the range as the new topIndex.
1012              */
1013
1014             resetView = 1;
1015             line = line1;
1016             charIndex = index1.charIndex;
1017         } else if (index1.linePtr == textPtr->topIndex.linePtr) {
1018             /*
1019              * Deletion range starts on top line but after topIndex.
1020              * Use the current topIndex as the new one.
1021              */
1022
1023             resetView = 1;
1024             line = line1;
1025             charIndex = textPtr->topIndex.charIndex;
1026         }
1027     } else if (index2.linePtr == textPtr->topIndex.linePtr) {
1028         /*
1029          * Deletion range ends on top line but before topIndex.
1030          * Figure out what will be the new character index for
1031          * the character currently pointed to by topIndex.
1032          */
1033
1034         resetView = 1;
1035         line = line2;
1036         charIndex = textPtr->topIndex.charIndex;
1037         if (index1.linePtr != index2.linePtr) {
1038             charIndex -= index2.charIndex;
1039         } else {
1040             charIndex -= (index2.charIndex - index1.charIndex);
1041         }
1042     }
1043     CkBTreeDeleteChars(&index1, &index2);
1044     if (resetView) {
1045 #if CK_USE_UTF
1046         CkTextMakeByteIndex(textPtr->tree, line, charIndex, &index1);
1047 #else
1048         CkTextMakeIndex(textPtr->tree, line, charIndex, &index1);
1049 #endif
1050         CkTextSetYView(textPtr, &index1, 0);
1051     }
1052
1053     /*
1054      * Invalidate any selection retrievals in progress.
1055      */
1056
1057     textPtr->abortSelections = 1;
1058
1059     return TCL_OK;
1060 }
1061 \f
1062 /*
1063  *----------------------------------------------------------------------
1064  *
1065  * TextSearchCmd --
1066  *
1067  *      This procedure is invoked to process the "search" widget command
1068  *      for text widgets.  See the user documentation for details on what
1069  *      it does.
1070  *
1071  * Results:
1072  *      A standard Tcl result.
1073  *
1074  * Side effects:
1075  *      See the user documentation.
1076  *
1077  *----------------------------------------------------------------------
1078  */
1079
1080 static int
1081 TextSearchCmd(textPtr, interp, argc, argv)
1082     CkText *textPtr;            /* Information about text widget. */
1083     Tcl_Interp *interp;         /* Current interpreter. */
1084     int argc;                   /* Number of arguments. */
1085     char **argv;                /* Argument strings. */
1086 {
1087     int backwards, exact, c, i, argsLeft, noCase, leftToScan;
1088     size_t length;
1089     int numLines, startingLine, startingChar, lineNum, firstChar, lastChar;
1090     int code, matchLength, matchChar, passes, stopLine, searchWholeText;
1091     int patLength;
1092     char *arg, *pattern, *varName, *p, *startOfLine;
1093     char buffer[20];
1094     CkTextIndex index, stopIndex;
1095     Tcl_DString line, patDString;
1096     CkTextSegment *segPtr;
1097     CkTextLine *linePtr;
1098     Tcl_RegExp regexp = NULL;           /* Initialization needed only to
1099                                          * prevent compiler warning. */
1100
1101     /*
1102      * Parse switches and other arguments.
1103      */
1104
1105     exact = 1;
1106     backwards = 0;
1107     noCase = 0;
1108     varName = NULL;
1109     for (i = 2; i < argc; i++) {
1110         arg = argv[i];
1111         if (arg[0] != '-') {
1112             break;
1113         }
1114         length = strlen(arg);
1115         if (length < 2) {
1116             badSwitch:
1117             Tcl_AppendResult(interp, "bad switch \"", arg,
1118                     "\": must be -forward, -backward, -exact, -regexp, ",
1119                     "-nocase, -count, or --", (char *) NULL);
1120             return TCL_ERROR;
1121         }
1122         c = arg[1];
1123         if ((c == 'b') && (strncmp(argv[i], "-backwards", length) == 0)) {
1124             backwards = 1;
1125         } else if ((c == 'c') && (strncmp(argv[i], "-count", length) == 0)) {
1126             if (i >= (argc-1)) {
1127                 interp->result = "no value given for \"-count\" option";
1128                 return TCL_ERROR;
1129             }
1130             i++;
1131             varName = argv[i];
1132         } else if ((c == 'e') && (strncmp(argv[i], "-exact", length) == 0)) {
1133             exact = 1;
1134         } else if ((c == 'f') && (strncmp(argv[i], "-forwards", length) == 0)) {
1135             backwards = 0;
1136         } else if ((c == 'n') && (strncmp(argv[i], "-nocase", length) == 0)) {
1137             noCase = 1;
1138         } else if ((c == 'r') && (strncmp(argv[i], "-regexp", length) == 0)) {
1139             exact = 0;
1140         } else if ((c == '-') && (strncmp(argv[i], "--", length) == 0)) {
1141             i++;
1142             break;
1143         } else {
1144             goto badSwitch;
1145         }
1146     }
1147     argsLeft = argc - (i+2);
1148     if ((argsLeft != 0) && (argsLeft != 1)) {
1149         Tcl_AppendResult(interp, "wrong # args: should be \"",
1150                 argv[0], " search ?switches? pattern index ?stopIndex?",
1151                 (char *) NULL);
1152         return TCL_ERROR;
1153     }
1154     pattern = argv[i];
1155
1156     /*
1157      * Convert the pattern to lower-case if we're supposed to ignore case.
1158      */
1159
1160     if (noCase) {
1161         Tcl_DStringInit(&patDString);
1162         Tcl_DStringAppend(&patDString, pattern, -1);
1163         pattern = Tcl_DStringValue(&patDString);
1164 #if CK_USE_UTF
1165         Tcl_UtfToLower(pattern);
1166 #else
1167         for (p = pattern; *p != 0; p++) {
1168             if (isupper((unsigned char) *p)) {
1169                 *p = tolower((unsigned char) *p);
1170             }
1171         }
1172 #endif
1173     }
1174
1175     if (CkTextGetIndex(interp, textPtr, argv[i+1], &index) != TCL_OK) {
1176         return TCL_ERROR;
1177     }
1178     numLines = CkBTreeNumLines(textPtr->tree);
1179     startingLine = CkBTreeLineIndex(index.linePtr);
1180     startingChar = index.charIndex;
1181     if (startingLine >= numLines) {
1182         if (backwards) {
1183             startingLine = CkBTreeNumLines(textPtr->tree) - 1;
1184             startingChar = CkBTreeCharsInLine(CkBTreeFindLine(textPtr->tree,
1185                     startingLine));
1186         } else {
1187             startingLine = 0;
1188             startingChar = 0;
1189         }
1190     }
1191     if (argsLeft == 1) {
1192         if (CkTextGetIndex(interp, textPtr, argv[i+2], &stopIndex) != TCL_OK) {
1193             return TCL_ERROR;
1194         }
1195         stopLine = CkBTreeLineIndex(stopIndex.linePtr);
1196         if (!backwards && (stopLine == numLines)) {
1197             stopLine = numLines-1;
1198         }
1199         searchWholeText = 0;
1200     } else {
1201         stopLine = 0;
1202         searchWholeText = 1;
1203     }
1204
1205     /*
1206      * Scan through all of the lines of the text circularly, starting
1207      * at the given index.
1208      */
1209
1210     matchLength = patLength = 0;        /* Only needed to prevent compiler
1211                                          * warnings. */
1212     if (exact) {
1213         patLength = strlen(pattern);
1214     } else {
1215         regexp = Tcl_RegExpCompile(interp, pattern);
1216         if (regexp == NULL) {
1217             return TCL_ERROR;
1218         }
1219     }
1220     lineNum = startingLine;
1221     code = TCL_OK;
1222     Tcl_DStringInit(&line);
1223     for (passes = 0; passes < 2; ) {
1224         if (lineNum >= numLines) {
1225             /*
1226              * Don't search the dummy last line of the text.
1227              */
1228
1229             goto nextLine;
1230         }
1231
1232         /*
1233          * Extract the text from the line.  If we're doing regular
1234          * expression matching, drop the newline from the line, so
1235          * that "$" can be used to match the end of the line.
1236          */
1237
1238         linePtr = CkBTreeFindLine(textPtr->tree, lineNum);
1239         for (segPtr = linePtr->segPtr; segPtr != NULL;
1240                 segPtr = segPtr->nextPtr) {
1241             if (segPtr->typePtr != &ckTextCharType) {
1242                 continue;
1243             }
1244             Tcl_DStringAppend(&line, segPtr->body.chars, segPtr->size);
1245         }
1246         if (!exact) {
1247             Tcl_DStringSetLength(&line, Tcl_DStringLength(&line)-1);
1248         }
1249         startOfLine = Tcl_DStringValue(&line);
1250
1251         /*
1252          * If we're ignoring case, convert the line to lower case.
1253          */
1254
1255         if (noCase) {
1256 #if CK_USE_UTF
1257             Tcl_DStringSetLength(&line,
1258                 Tcl_UtfToLower(Tcl_DStringValue(&line)));
1259 #else
1260             for (p = Tcl_DStringValue(&line); *p != 0; p++) {
1261                 if (isupper((unsigned char) *p)) {
1262                     *p = tolower((unsigned char) *p);
1263                 }
1264             }
1265 #endif
1266         }
1267
1268         /*
1269          * Check for matches within the current line.  If so, and if we're
1270          * searching backwards, repeat the search to find the last match
1271          * in the line.
1272          */
1273
1274         matchChar = -1;
1275         firstChar = 0;
1276         lastChar = INT_MAX;
1277         if (lineNum == startingLine) {
1278             int indexInDString;
1279
1280             /*
1281              * The starting line is tricky: the first time we see it
1282              * we check one part of the line, and the second pass through
1283              * we check the other part of the line.  We have to be very
1284              * careful here because there could be embedded windows or
1285              * other things that are not in the extracted line.  Rescan
1286              * the original line to compute the index in it of the first
1287              * character.
1288              */
1289
1290             indexInDString = startingChar;
1291             for (segPtr = linePtr->segPtr, leftToScan = startingChar;
1292                     leftToScan > 0; segPtr = segPtr->nextPtr) {
1293                 if (segPtr->typePtr != &ckTextCharType) {
1294                     indexInDString -= segPtr->size;
1295                 }
1296                 leftToScan -= segPtr->size;
1297             }
1298
1299             passes++;
1300             if ((passes == 1) ^ backwards) {
1301                 /*
1302                  * Only use the last part of the line.
1303                  */
1304
1305                 firstChar = indexInDString;
1306                 if (firstChar >= Tcl_DStringLength(&line)) {
1307                     goto nextLine;
1308                 }
1309             } else {
1310                 /*
1311                  * Use only the first part of the line.
1312                  */
1313
1314                 lastChar = indexInDString;
1315             }
1316         }
1317         do {
1318             int thisLength;
1319 #if CK_USE_UTF
1320             Tcl_UniChar ch;
1321 #endif
1322
1323             if (exact) {
1324                 p = strstr(startOfLine + firstChar, pattern);
1325                 if (p == NULL) {
1326                     break;
1327                 }
1328                 i = p - startOfLine;
1329                 thisLength = patLength;
1330             } else {
1331                 char *start, *end;
1332                 int match;
1333
1334                 match = Tcl_RegExpExec(interp, regexp,
1335                         startOfLine + firstChar, startOfLine);
1336                 if (match < 0) {
1337                     code = TCL_ERROR;
1338                     goto done;
1339                 }
1340                 if (!match) {
1341                     break;
1342                 }
1343                 Tcl_RegExpRange(regexp, 0, &start, &end);
1344                 i = start - startOfLine;
1345                 thisLength = end - start;
1346             }
1347             if (i >= lastChar) {
1348                 break;
1349             }
1350             matchChar = i;
1351             matchLength = thisLength;
1352 #if CK_USE_UTF
1353             firstChar = i + Tcl_UtfToUniChar(startOfLine + matchChar, &ch);
1354 #else
1355             firstChar = matchChar+1;
1356 #endif
1357         } while (backwards);
1358
1359         /*
1360          * If we found a match then we're done.  Make sure that
1361          * the match occurred before the stopping index, if one was
1362          * specified.
1363          */
1364
1365         if (matchChar >= 0) {
1366 #if CK_USE_UTF
1367             int numChars;
1368            
1369             numChars = Tcl_NumUtfChars(startOfLine + matchChar,
1370                 matchLength);
1371 #endif
1372             
1373             /*
1374              * The index information returned by the regular expression
1375              * parser only considers textual information:  it doesn't
1376              * account for embedded windows or any other non-textual info.
1377              * Scan through the line's segments again to adjust both
1378              * matchChar and matchCount.
1379              */
1380
1381             for (segPtr = linePtr->segPtr, leftToScan = matchChar;
1382                     leftToScan >= 0; segPtr = segPtr->nextPtr) {
1383                 if (segPtr->typePtr != &ckTextCharType) {
1384                     matchChar += segPtr->size;
1385                     continue;
1386                 }
1387                 leftToScan -= segPtr->size;
1388             }
1389             for (leftToScan += matchLength; leftToScan > 0;
1390                     segPtr = segPtr->nextPtr) {
1391                 if (segPtr->typePtr != &ckTextCharType) {
1392 #if CK_USE_UTF
1393                     numChars += segPtr->size;
1394 #else
1395                     matchLength += segPtr->size;
1396 #endif
1397                     continue;
1398                 }
1399                 leftToScan -= segPtr->size;
1400             }
1401 #if CK_USE_UTF
1402             CkTextMakeByteIndex(textPtr->tree, lineNum, matchChar, &index);
1403 #else
1404             CkTextMakeIndex(textPtr->tree, lineNum, matchChar, &index);
1405 #endif
1406             if (!searchWholeText) {
1407                 if (!backwards && (CkTextIndexCmp(&index, &stopIndex) >= 0)) {
1408                     goto done;
1409                 }
1410                 if (backwards && (CkTextIndexCmp(&index, &stopIndex) < 0)) {
1411                     goto done;
1412                 }
1413             }
1414             if (varName != NULL) {
1415 #if CK_USE_UTF
1416                 sprintf(buffer, "%d", numChars);
1417 #else
1418                 sprintf(buffer, "%d", matchLength);
1419 #endif
1420                 if (Tcl_SetVar(interp, varName, buffer, TCL_LEAVE_ERR_MSG)
1421                         == NULL) {
1422                     code = TCL_ERROR;
1423                     goto done;
1424                 }
1425             }
1426             CkTextPrintIndex(&index, interp->result);
1427             goto done;
1428         }
1429
1430         /*
1431          * Go to the next (or previous) line;
1432          */
1433
1434         nextLine:
1435         if (backwards) {
1436             lineNum--;
1437             if (!searchWholeText) {
1438                 if (lineNum < stopLine) {
1439                     break;
1440                 }
1441             } else if (lineNum < 0) {
1442                 lineNum = numLines-1;
1443             }
1444         } else {
1445             lineNum++;
1446             if (!searchWholeText) {
1447                 if (lineNum > stopLine) {
1448                     break;
1449                 }
1450             } else if (lineNum >= numLines) {
1451                 lineNum = 0;
1452             }
1453         }
1454         Tcl_DStringSetLength(&line, 0);
1455     }
1456     done:
1457     Tcl_DStringFree(&line);
1458     if (noCase) {
1459         Tcl_DStringFree(&patDString);
1460     }
1461     return code;
1462 }
1463 \f
1464 /*
1465  *----------------------------------------------------------------------
1466  *
1467  * CkTextGetTabs --
1468  *
1469  *      Parses a string description of a set of tab stops.
1470  *
1471  * Results:
1472  *      The return value is a pointer to a malloc'ed structure holding
1473  *      parsed information about the tab stops.  If an error occurred
1474  *      then the return value is NULL and an error message is left in
1475  *      interp->result.
1476  *
1477  * Side effects:
1478  *      Memory is allocated for the structure that is returned.  It is
1479  *      up to the caller to free this structure when it is no longer
1480  *      needed.
1481  *
1482  *----------------------------------------------------------------------
1483  */
1484
1485 CkTextTabArray *
1486 CkTextGetTabs(interp, winPtr, string)
1487     Tcl_Interp *interp;                 /* Used for error reporting. */
1488     CkWindow *winPtr;                   /* Window in which the tabs will be
1489                                          * used. */
1490     char *string;                       /* Description of the tab stops.  See
1491                                          * text manual entry for details. */
1492 {
1493     int argc, i, count, c;
1494     char **argv;
1495     CkTextTabArray *tabArrayPtr;
1496     CkTextTab *tabPtr;
1497 #if CK_USE_UTF
1498     Tcl_UniChar ch;
1499 #endif
1500
1501     if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
1502         return NULL;
1503     }
1504
1505     /*
1506      * First find out how many entries we need to allocate in the
1507      * tab array.
1508      */
1509
1510     count = 0;
1511     for (i = 0; i < argc; i++) {
1512         c = argv[i][0];
1513         if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
1514             count++;
1515         }
1516     }
1517
1518     /*
1519      * Parse the elements of the list one at a time to fill in the
1520      * array.
1521      */
1522
1523     tabArrayPtr = (CkTextTabArray *) ckalloc((unsigned)
1524             (sizeof(CkTextTabArray) + (count-1)*sizeof(CkTextTab)));
1525     tabArrayPtr->numTabs = 0;
1526     for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i  < argc; i++, tabPtr++) {
1527         if (Ck_GetCoord(interp, winPtr, argv[i], &tabPtr->location)
1528                 != TCL_OK) {
1529             goto error;
1530         }
1531         tabArrayPtr->numTabs++;
1532
1533         /*
1534          * See if there is an explicit alignment in the next list
1535          * element.  Otherwise just use "left".
1536          */
1537
1538         tabPtr->alignment = LEFT;
1539         if ((i+1) == argc) {
1540             continue;
1541         }
1542 #if CK_USE_UTF
1543         Tcl_UtfToUniChar(argv[i+1], &ch);
1544         if (!Tcl_UniCharIsAlpha(ch)) {
1545             continue;
1546         }
1547 #else
1548         c = (unsigned char) argv[i+1][0];
1549         if (!isalpha(c)) {
1550             continue;
1551         }
1552 #endif
1553         i += 1;
1554         if ((c == 'l') && (strncmp(argv[i], "left",
1555                 strlen(argv[i])) == 0)) {
1556             tabPtr->alignment = LEFT;
1557         } else if ((c == 'r') && (strncmp(argv[i], "right",
1558                 strlen(argv[i])) == 0)) {
1559             tabPtr->alignment = RIGHT;
1560         } else if ((c == 'c') && (strncmp(argv[i], "center",
1561                 strlen(argv[i])) == 0)) {
1562             tabPtr->alignment = CENTER;
1563         } else if ((c == 'n') && (strncmp(argv[i],
1564                 "numeric", strlen(argv[i])) == 0)) {
1565             tabPtr->alignment = NUMERIC;
1566         } else {
1567             Tcl_AppendResult(interp, "bad tab alignment \"",
1568                     argv[i], "\": must be left, right, center, or numeric",
1569                     (char *) NULL);
1570             goto error;
1571         }
1572     }
1573     ckfree((char *) argv);
1574     return tabArrayPtr;
1575
1576     error:
1577     ckfree((char *) tabArrayPtr);
1578     ckfree((char *) argv);
1579     return NULL;
1580 }