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.
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
14 * See the file "license.terms" for information on usage and redistribution
15 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
24 * Information used to parse text configuration options:
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,
36 {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
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),
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),
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),
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),
74 {CK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
75 DEF_TEXT_YSCROLL_COMMAND, Ck_Offset(CkText, yScrollCmd),
77 {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
82 * Ck_Uid's used to represent text states:
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;
92 * Boolean variable indicating whether or not special debugging code
99 * Forward declarations for procedures defined later in this file:
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,
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));
119 *--------------------------------------------------------------
123 * This procedure is invoked to process the "text" Tcl command.
124 * See the user documentation for details on what it does.
127 * A standard Tcl result.
130 * See the user documentation.
132 *--------------------------------------------------------------
136 Ck_TextCmd(clientData, interp, argc, argv)
137 ClientData clientData; /* Main window associated with
139 Tcl_Interp *interp; /* Current interpreter. */
140 int argc; /* Number of arguments. */
141 char **argv; /* Argument strings. */
143 CkWindow *mainPtr = (CkWindow *) clientData;
145 register CkText *textPtr;
146 CkTextIndex startIndex;
149 Tcl_AppendResult(interp, "wrong # args: should be \"",
150 argv[0], " pathName ?options?\"", (char *) NULL);
155 * Perform once-only initialization:
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");
170 new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 0);
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,
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;
190 textPtr->tabOptionString = NULL;
191 textPtr->tabArrayPtr = NULL;
192 textPtr->wrapMode = ckTextCharUid;
195 textPtr->prevWidth = new->width;
196 textPtr->prevHeight = new->height;
197 CkTextCreateDInfo(textPtr);
199 CkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex);
201 CkTextMakeIndex(textPtr->tree, 0, 0, &startIndex);
203 CkTextSetYView(textPtr, &startIndex, 0);
204 textPtr->selTagPtr = NULL;
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;
221 * Create the "sel" tag and the "current" and "insert" marks.
224 textPtr->selTagPtr = CkTextCreateTag(textPtr, "sel");
225 textPtr->currentMarkPtr = CkTextSetMark(textPtr, "current", &startIndex);
226 textPtr->insertMarkPtr = CkTextSetMark(textPtr, "insert", &startIndex);
228 Ck_SetClass(new, "Text");
229 Ck_CreateEventHandler(textPtr->winPtr,
230 CK_EV_EXPOSE | CK_EV_DESTROY | CK_EV_MAP | CK_EV_FOCUSIN |
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);
239 interp->result = textPtr->winPtr->pathName;
245 *--------------------------------------------------------------
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.
254 * A standard Tcl result.
257 * See the user documentation.
259 *--------------------------------------------------------------
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. */
269 register CkText *textPtr = (CkText *) clientData;
273 CkTextIndex index1, index2;
276 Tcl_AppendResult(interp, "wrong # args: should be \"",
277 argv[0], " option ?arg arg ...?\"", (char *) NULL);
280 Ck_Preserve((ClientData) textPtr);
282 length = strlen(argv[1]);
283 if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
284 int x, y, width, height;
287 Tcl_AppendResult(interp, "wrong # args: should be \"",
288 argv[0], " bbox index\"", (char *) NULL);
292 if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
296 if (CkTextCharBbox(textPtr, &index1, &x, &y, &width, &height) == 0) {
297 sprintf(interp->result, "%d %d %d %d", x, y, width, height);
299 } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
302 Tcl_AppendResult(interp, "wrong # args: should be \"",
303 argv[0], " cget option\"",
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)
316 Tcl_AppendResult(interp, "wrong # args: should be \"",
317 argv[0], " compare index1 op index2\"", (char *) NULL);
321 if ((CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK)
322 || (CkTextGetIndex(interp, textPtr, argv[4], &index2)
327 relation = CkTextIndexCmp(&index1, &index2);
330 value = (relation < 0);
331 if ((p[1] == '=') && (p[2] == 0)) {
332 value = (relation <= 0);
333 } else if (p[1] != 0) {
335 Tcl_AppendResult(interp, "bad comparison operator \"",
336 argv[3], "\": must be <, <=, ==, >=, >, or !=",
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) {
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);
355 interp->result = (value) ? "1" : "0";
356 } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
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);
365 result = ConfigureText(interp, textPtr, argc-2, argv+2,
366 CK_CONFIG_ARGV_ONLY);
368 } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
371 Tcl_AppendResult(interp, "wrong # args: should be \"",
372 argv[0], " debug boolean\"", (char *) NULL);
377 interp->result = (ckBTreeDebug) ? "1" : "0";
379 if (Tcl_GetBoolean(interp, argv[2], &ckBTreeDebug) != TCL_OK) {
383 ckTextDebug = ckBTreeDebug;
385 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
387 if ((argc != 3) && (argc != 4)) {
388 Tcl_AppendResult(interp, "wrong # args: should be \"",
389 argv[0], " delete index1 ?index2?\"", (char *) NULL);
393 if (textPtr->state == ckTextNormalUid) {
394 result = DeleteChars(textPtr, argv[2],
395 (argc == 4) ? argv[3] : (char *) NULL);
397 } else if ((c == 'd') && (strncmp(argv[1], "dlineinfo", length) == 0)
399 int x, y, width, height, base;
402 Tcl_AppendResult(interp, "wrong # args: should be \"",
403 argv[0], " dlineinfo index\"", (char *) NULL);
407 if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
411 if (CkTextDLineInfo(textPtr, &index1, &x, &y, &width, &height, &base)
413 sprintf(interp->result, "%d %d %d %d %d", x, y, width,
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);
423 if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
429 CkTextIndexForwChars(&index2, 1, &index2);
430 } else if (CkTextGetIndex(interp, textPtr, argv[3], &index2)
435 if (CkTextIndexCmp(&index1, &index2) >= 0) {
439 int offset, last, savedChar;
440 CkTextSegment *segPtr;
442 segPtr = CkTextIndexToSeg(&index1, &offset);
444 if (index1.linePtr == index2.linePtr) {
447 if (index2.charIndex == index1.charIndex) {
450 last2 = index2.charIndex - index1.charIndex + offset;
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,
460 segPtr->body.chars[last] = savedChar;
462 CkTextIndexForwChars(&index1, last-offset, &index1);
464 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
467 Tcl_AppendResult(interp, "wrong # args: should be \"",
468 argv[0], " index index\"",
473 if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
477 CkTextPrintIndex(&index1, interp->result);
478 } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
482 CkTextTag **oldTagArrayPtr;
485 Tcl_AppendResult(interp, "wrong # args: should be \"",
487 " insert index chars ?tagList chars tagList ...?\"",
492 if (CkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
496 if (textPtr->state == ckTextNormalUid) {
497 for (j = 3; j < argc; j += 2) {
498 InsertChars(textPtr, &index1, argv[j]);
500 CkTextIndexForwChars(&index1, (int) strlen(argv[j]),
502 oldTagArrayPtr = CkBTreeGetTags(&index1, &numTags);
503 if (oldTagArrayPtr != NULL) {
504 for (i = 0; i < numTags; i++) {
505 CkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
507 ckfree((char *) oldTagArrayPtr);
509 if (Tcl_SplitList(interp, argv[j+1], &numTags, &tagNames)
514 for (i = 0; i < numTags; i++) {
515 CkBTreeTag(&index1, &index2,
516 CkTextCreateTag(textPtr, tagNames[i]), 1);
518 ckfree((char *) tagNames);
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)
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);
533 } else if ((c == 'w') && (strncmp(argv[1], "window", length) == 0)) {
534 result = CkTextWindowCmd(textPtr, interp, argc, argv);
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)
540 result = CkTextYviewCmd(textPtr, interp, argc, argv);
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",
551 Ck_Release((ClientData) textPtr);
556 *----------------------------------------------------------------------
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).
568 * Everything associated with the text is freed up.
570 *----------------------------------------------------------------------
574 DestroyText(clientData)
575 ClientData clientData; /* Info about text widget. */
577 register CkText *textPtr = (CkText *) clientData;
578 Tcl_HashSearch search;
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.
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);
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));
602 Tcl_DeleteHashTable(&textPtr->markTable);
603 if (textPtr->tabArrayPtr != NULL) {
604 ckfree((char *) textPtr->tabArrayPtr);
606 if (textPtr->bindingTable != NULL) {
607 Ck_DeleteBindingTable(textPtr->bindingTable);
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.
616 Ck_FreeOptions(configSpecs, (char *) textPtr, 0);
617 ckfree((char *) textPtr);
621 *----------------------------------------------------------------------
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.
630 * The return value is a standard Tcl result. If TCL_ERROR is
631 * returned, then interp->result contains an error message.
634 * Configuration information, such as text string, colors, font,
635 * etc. get set for textPtr; old resources get freed, if there
638 *----------------------------------------------------------------------
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. */
650 if (Ck_ConfigureWidget(interp, textPtr->winPtr, configSpecs,
651 argc, argv, (char *) textPtr, flags) != TCL_OK) {
656 * A few other options also need special processing, such as parsing
657 * the geometry and setting the background from a 3-D border.
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;
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;
681 if (textPtr->tabArrayPtr != NULL) {
682 ckfree((char *) textPtr->tabArrayPtr);
683 textPtr->tabArrayPtr = NULL;
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)");
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
702 textPtr->selTagPtr->bg = textPtr->selBg;
703 textPtr->selTagPtr->fg = textPtr->selFg;
704 textPtr->selTagPtr->attr = textPtr->selAttr;
705 textPtr->selTagPtr->affectsDisplay = 0;
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;
730 CkTextRedrawTag(textPtr, (CkTextIndex *) NULL, (CkTextIndex *) NULL,
731 textPtr->selTagPtr, 1);
734 * Register the desired geometry for the window, and arrange for
735 * the window to be redisplayed.
738 if (textPtr->width <= 0) {
741 if (textPtr->height <= 0) {
744 Ck_GeometryRequest(textPtr->winPtr, textPtr->width, textPtr->height);
746 CkTextRelayoutWindow(textPtr);
751 *--------------------------------------------------------------
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.
763 * When the window gets deleted, internal structures get
764 * cleaned up. When it gets exposed, it is redisplayed.
766 *--------------------------------------------------------------
770 TextEventProc(clientData, eventPtr)
771 ClientData clientData; /* Information about window. */
772 register CkEvent *eventPtr; /* Information about event. */
774 register CkText *textPtr = (CkText *) clientData;
775 CkWindow *winPtr = textPtr->winPtr;
776 CkTextIndex index, index2;
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;
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));
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;
799 textPtr->flags &= ~GOT_FOCUS;
801 CkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
802 CkTextIndexForwChars(&index, 1, &index2);
803 CkTextChanged(textPtr, &index, &index2);
804 CkTextRedrawRegion(textPtr, 0, 0, winPtr->width,
810 *----------------------------------------------------------------------
812 * TextCmdDeletedProc --
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.
822 * The widget is destroyed.
824 *----------------------------------------------------------------------
828 TextCmdDeletedProc(clientData)
829 ClientData clientData; /* Pointer to widget record for widget. */
831 CkText *textPtr = (CkText *) clientData;
832 CkWindow *winPtr = textPtr->winPtr;
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.
841 if (winPtr != NULL) {
842 textPtr->winPtr = NULL;
843 Ck_DestroyWindow(winPtr);
848 *----------------------------------------------------------------------
852 * This procedure implements most of the functionality of the
853 * "insert" widget command.
859 * The characters in "string" get added to the text just before
860 * the character indicated by "indexPtr".
862 *----------------------------------------------------------------------
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. */
876 * Don't allow insertions on the last (dummy) line of the text.
879 lineIndex = CkBTreeLineIndex(indexPtr->linePtr);
880 if (lineIndex == CkBTreeNumLines(textPtr->tree)) {
883 CkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
885 CkTextMakeIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
890 * Notify the display module that lines are about to change, then do
894 CkTextChanged(textPtr, indexPtr, indexPtr);
895 CkBTreeInsertChars(indexPtr, string);
898 * Invalidate any selection retrievals in progress.
901 textPtr->abortSelections = 1;
905 *----------------------------------------------------------------------
909 * This procedure implements most of the functionality of the
910 * "delete" widget command.
913 * Returns a standard Tcl result, and leaves an error message
914 * in textPtr->interp if there is an error.
917 * Characters get deleted from the text.
919 *----------------------------------------------------------------------
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
932 int line1, line2, line, charIndex, resetView;
933 CkTextIndex index1, index2;
936 * Parse the starting and stopping indices.
939 if (CkTextGetIndex(textPtr->interp, textPtr, index1String, &index1)
943 if (index2String != NULL) {
944 if (CkTextGetIndex(textPtr->interp, textPtr, index2String, &index2)
950 CkTextIndexForwChars(&index2, 1, &index2);
954 * Make sure there's really something to delete.
957 if (CkTextIndexCmp(&index1, &index2) >= 0) {
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).
973 line1 = CkBTreeLineIndex(index1.linePtr);
974 line2 = CkBTreeLineIndex(index2.linePtr);
975 if (line2 == CkBTreeNumLines(textPtr->tree)) {
976 CkTextTag **arrayPtr;
978 CkTextIndex oldIndex2;
981 CkTextIndexBackChars(&oldIndex2, 1, &index2);
983 if ((index1.charIndex == 0) && (line1 != 0)) {
984 CkTextIndexBackChars(&index1, 1, &index1);
987 arrayPtr = CkBTreeGetTags(&index2, &arraySize);
988 if (arrayPtr != NULL) {
989 for (i = 0; i < arraySize; i++) {
990 CkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
992 ckfree((char *) arrayPtr);
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.
1005 CkTextChanged(textPtr, &index1, &index2);
1006 resetView = line = charIndex = 0;
1007 if (CkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) {
1008 if (CkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) {
1010 * Deletion range straddles topIndex: use the beginning
1011 * of the range as the new topIndex.
1016 charIndex = index1.charIndex;
1017 } else if (index1.linePtr == textPtr->topIndex.linePtr) {
1019 * Deletion range starts on top line but after topIndex.
1020 * Use the current topIndex as the new one.
1025 charIndex = textPtr->topIndex.charIndex;
1027 } else if (index2.linePtr == textPtr->topIndex.linePtr) {
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.
1036 charIndex = textPtr->topIndex.charIndex;
1037 if (index1.linePtr != index2.linePtr) {
1038 charIndex -= index2.charIndex;
1040 charIndex -= (index2.charIndex - index1.charIndex);
1043 CkBTreeDeleteChars(&index1, &index2);
1046 CkTextMakeByteIndex(textPtr->tree, line, charIndex, &index1);
1048 CkTextMakeIndex(textPtr->tree, line, charIndex, &index1);
1050 CkTextSetYView(textPtr, &index1, 0);
1054 * Invalidate any selection retrievals in progress.
1057 textPtr->abortSelections = 1;
1063 *----------------------------------------------------------------------
1067 * This procedure is invoked to process the "search" widget command
1068 * for text widgets. See the user documentation for details on what
1072 * A standard Tcl result.
1075 * See the user documentation.
1077 *----------------------------------------------------------------------
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. */
1087 int backwards, exact, c, i, argsLeft, noCase, leftToScan;
1089 int numLines, startingLine, startingChar, lineNum, firstChar, lastChar;
1090 int code, matchLength, matchChar, passes, stopLine, searchWholeText;
1092 char *arg, *pattern, *varName, *p, *startOfLine;
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. */
1102 * Parse switches and other arguments.
1109 for (i = 2; i < argc; i++) {
1111 if (arg[0] != '-') {
1114 length = strlen(arg);
1117 Tcl_AppendResult(interp, "bad switch \"", arg,
1118 "\": must be -forward, -backward, -exact, -regexp, ",
1119 "-nocase, -count, or --", (char *) NULL);
1123 if ((c == 'b') && (strncmp(argv[i], "-backwards", length) == 0)) {
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";
1132 } else if ((c == 'e') && (strncmp(argv[i], "-exact", length) == 0)) {
1134 } else if ((c == 'f') && (strncmp(argv[i], "-forwards", length) == 0)) {
1136 } else if ((c == 'n') && (strncmp(argv[i], "-nocase", length) == 0)) {
1138 } else if ((c == 'r') && (strncmp(argv[i], "-regexp", length) == 0)) {
1140 } else if ((c == '-') && (strncmp(argv[i], "--", length) == 0)) {
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?",
1157 * Convert the pattern to lower-case if we're supposed to ignore case.
1161 Tcl_DStringInit(&patDString);
1162 Tcl_DStringAppend(&patDString, pattern, -1);
1163 pattern = Tcl_DStringValue(&patDString);
1165 Tcl_UtfToLower(pattern);
1167 for (p = pattern; *p != 0; p++) {
1168 if (isupper((unsigned char) *p)) {
1169 *p = tolower((unsigned char) *p);
1175 if (CkTextGetIndex(interp, textPtr, argv[i+1], &index) != TCL_OK) {
1178 numLines = CkBTreeNumLines(textPtr->tree);
1179 startingLine = CkBTreeLineIndex(index.linePtr);
1180 startingChar = index.charIndex;
1181 if (startingLine >= numLines) {
1183 startingLine = CkBTreeNumLines(textPtr->tree) - 1;
1184 startingChar = CkBTreeCharsInLine(CkBTreeFindLine(textPtr->tree,
1191 if (argsLeft == 1) {
1192 if (CkTextGetIndex(interp, textPtr, argv[i+2], &stopIndex) != TCL_OK) {
1195 stopLine = CkBTreeLineIndex(stopIndex.linePtr);
1196 if (!backwards && (stopLine == numLines)) {
1197 stopLine = numLines-1;
1199 searchWholeText = 0;
1202 searchWholeText = 1;
1206 * Scan through all of the lines of the text circularly, starting
1207 * at the given index.
1210 matchLength = patLength = 0; /* Only needed to prevent compiler
1213 patLength = strlen(pattern);
1215 regexp = Tcl_RegExpCompile(interp, pattern);
1216 if (regexp == NULL) {
1220 lineNum = startingLine;
1222 Tcl_DStringInit(&line);
1223 for (passes = 0; passes < 2; ) {
1224 if (lineNum >= numLines) {
1226 * Don't search the dummy last line of the text.
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.
1238 linePtr = CkBTreeFindLine(textPtr->tree, lineNum);
1239 for (segPtr = linePtr->segPtr; segPtr != NULL;
1240 segPtr = segPtr->nextPtr) {
1241 if (segPtr->typePtr != &ckTextCharType) {
1244 Tcl_DStringAppend(&line, segPtr->body.chars, segPtr->size);
1247 Tcl_DStringSetLength(&line, Tcl_DStringLength(&line)-1);
1249 startOfLine = Tcl_DStringValue(&line);
1252 * If we're ignoring case, convert the line to lower case.
1257 Tcl_DStringSetLength(&line,
1258 Tcl_UtfToLower(Tcl_DStringValue(&line)));
1260 for (p = Tcl_DStringValue(&line); *p != 0; p++) {
1261 if (isupper((unsigned char) *p)) {
1262 *p = tolower((unsigned char) *p);
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
1277 if (lineNum == startingLine) {
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
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;
1296 leftToScan -= segPtr->size;
1300 if ((passes == 1) ^ backwards) {
1302 * Only use the last part of the line.
1305 firstChar = indexInDString;
1306 if (firstChar >= Tcl_DStringLength(&line)) {
1311 * Use only the first part of the line.
1314 lastChar = indexInDString;
1324 p = strstr(startOfLine + firstChar, pattern);
1328 i = p - startOfLine;
1329 thisLength = patLength;
1334 match = Tcl_RegExpExec(interp, regexp,
1335 startOfLine + firstChar, startOfLine);
1343 Tcl_RegExpRange(regexp, 0, &start, &end);
1344 i = start - startOfLine;
1345 thisLength = end - start;
1347 if (i >= lastChar) {
1351 matchLength = thisLength;
1353 firstChar = i + Tcl_UtfToUniChar(startOfLine + matchChar, &ch);
1355 firstChar = matchChar+1;
1357 } while (backwards);
1360 * If we found a match then we're done. Make sure that
1361 * the match occurred before the stopping index, if one was
1365 if (matchChar >= 0) {
1369 numChars = Tcl_NumUtfChars(startOfLine + matchChar,
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.
1381 for (segPtr = linePtr->segPtr, leftToScan = matchChar;
1382 leftToScan >= 0; segPtr = segPtr->nextPtr) {
1383 if (segPtr->typePtr != &ckTextCharType) {
1384 matchChar += segPtr->size;
1387 leftToScan -= segPtr->size;
1389 for (leftToScan += matchLength; leftToScan > 0;
1390 segPtr = segPtr->nextPtr) {
1391 if (segPtr->typePtr != &ckTextCharType) {
1393 numChars += segPtr->size;
1395 matchLength += segPtr->size;
1399 leftToScan -= segPtr->size;
1402 CkTextMakeByteIndex(textPtr->tree, lineNum, matchChar, &index);
1404 CkTextMakeIndex(textPtr->tree, lineNum, matchChar, &index);
1406 if (!searchWholeText) {
1407 if (!backwards && (CkTextIndexCmp(&index, &stopIndex) >= 0)) {
1410 if (backwards && (CkTextIndexCmp(&index, &stopIndex) < 0)) {
1414 if (varName != NULL) {
1416 sprintf(buffer, "%d", numChars);
1418 sprintf(buffer, "%d", matchLength);
1420 if (Tcl_SetVar(interp, varName, buffer, TCL_LEAVE_ERR_MSG)
1426 CkTextPrintIndex(&index, interp->result);
1431 * Go to the next (or previous) line;
1437 if (!searchWholeText) {
1438 if (lineNum < stopLine) {
1441 } else if (lineNum < 0) {
1442 lineNum = numLines-1;
1446 if (!searchWholeText) {
1447 if (lineNum > stopLine) {
1450 } else if (lineNum >= numLines) {
1454 Tcl_DStringSetLength(&line, 0);
1457 Tcl_DStringFree(&line);
1459 Tcl_DStringFree(&patDString);
1465 *----------------------------------------------------------------------
1469 * Parses a string description of a set of tab stops.
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
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
1482 *----------------------------------------------------------------------
1486 CkTextGetTabs(interp, winPtr, string)
1487 Tcl_Interp *interp; /* Used for error reporting. */
1488 CkWindow *winPtr; /* Window in which the tabs will be
1490 char *string; /* Description of the tab stops. See
1491 * text manual entry for details. */
1493 int argc, i, count, c;
1495 CkTextTabArray *tabArrayPtr;
1501 if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
1506 * First find out how many entries we need to allocate in the
1511 for (i = 0; i < argc; i++) {
1513 if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
1519 * Parse the elements of the list one at a time to fill in the
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)
1531 tabArrayPtr->numTabs++;
1534 * See if there is an explicit alignment in the next list
1535 * element. Otherwise just use "left".
1538 tabPtr->alignment = LEFT;
1539 if ((i+1) == argc) {
1543 Tcl_UtfToUniChar(argv[i+1], &ch);
1544 if (!Tcl_UniCharIsAlpha(ch)) {
1548 c = (unsigned char) argv[i+1][0];
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;
1567 Tcl_AppendResult(interp, "bad tab alignment \"",
1568 argv[i], "\": must be left, right, center, or numeric",
1573 ckfree((char *) argv);
1577 ckfree((char *) tabArrayPtr);
1578 ckfree((char *) argv);