]> www.wagner.pp.ru Git - oss/ck.git/blob - ckTextTag.c
Ck console graphics toolkit
[oss/ck.git] / ckTextTag.c
1 /* 
2  * ckTextTag.c --
3  *
4  *      This module implements the "tag" subcommand of the widget command
5  *      for text widgets, plus most of the other high-level functions
6  *      related to tags.
7  *
8  * Copyright (c) 1992-1994 The Regents of the University of California.
9  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
10  * Copyright (c) 1995 Christian Werner
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15
16 #include "ckPort.h"
17 #include "ck.h"
18 #include "ckText.h"
19 #include "default.h"
20
21 /*
22  * Information used for parsing tag configuration information:
23  */
24
25 static Ck_ConfigSpec tagConfigSpecs[] = {
26     {CK_CONFIG_ATTR, "-attributes", (char *) NULL, (char *) NULL,
27         (char *) NULL, Ck_Offset(CkTextTag, attr), 0},
28     {CK_CONFIG_COLOR, "-background", (char *) NULL, (char *) NULL,
29         (char *) NULL, Ck_Offset(CkTextTag, bg), 0},
30     {CK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
31         (char *) NULL, Ck_Offset(CkTextTag, fg), 0},
32     {CK_CONFIG_STRING, "-justify", (char *) NULL, (char *) NULL,
33         (char *) NULL, Ck_Offset(CkTextTag, justifyString), CK_CONFIG_NULL_OK},
34     {CK_CONFIG_STRING, "-lmargin1", (char *) NULL, (char *) NULL,
35         (char *) NULL, Ck_Offset(CkTextTag, lMargin1String), CK_CONFIG_NULL_OK},
36     {CK_CONFIG_STRING, "-lmargin2", (char *) NULL, (char *) NULL,
37         (char *) NULL, Ck_Offset(CkTextTag, lMargin2String), CK_CONFIG_NULL_OK},
38     {CK_CONFIG_STRING, "-rmargin", (char *) NULL, (char *) NULL,
39         (char *) NULL, Ck_Offset(CkTextTag, rMarginString), CK_CONFIG_NULL_OK},
40     {CK_CONFIG_STRING, "-tabs", (char *) NULL, (char *) NULL,
41         (char *) NULL, Ck_Offset(CkTextTag, tabString), CK_CONFIG_NULL_OK},
42     {CK_CONFIG_UID, "-wrap", (char *) NULL, (char *) NULL,
43         (char *) NULL, Ck_Offset(CkTextTag, wrapMode),
44         CK_CONFIG_NULL_OK},
45     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
46         (char *) NULL, 0, 0}
47 };
48
49 /*
50  * Forward declarations for procedures defined later in this file:
51  */
52
53 static void             ChangeTagPriority _ANSI_ARGS_((CkText *textPtr,
54                             CkTextTag *tagPtr, int prio));
55 static CkTextTag *      FindTag _ANSI_ARGS_((Tcl_Interp *interp,
56                             CkText *textPtr, char *tagName));
57 static void             SortTags _ANSI_ARGS_((int numTags,
58                             CkTextTag **tagArrayPtr));
59 static int              TagSortProc _ANSI_ARGS_((CONST VOID *first,
60                             CONST VOID *second));
61 \f
62 /*
63  *--------------------------------------------------------------
64  *
65  * CkTextTagCmd --
66  *
67  *      This procedure is invoked to process the "tag" options of
68  *      the widget command for text widgets. See the user documentation
69  *      for details on what it does.
70  *
71  * Results:
72  *      A standard Tcl result.
73  *
74  * Side effects:
75  *      See the user documentation.
76  *
77  *--------------------------------------------------------------
78  */
79
80 int
81 CkTextTagCmd(textPtr, interp, argc, argv)
82     register CkText *textPtr;   /* Information about text widget. */
83     Tcl_Interp *interp;         /* Current interpreter. */
84     int argc;                   /* Number of arguments. */
85     char **argv;                /* Argument strings.  Someone else has already
86                                  * parsed this command enough to know that
87                                  * argv[1] is "tag". */
88 {
89     int c, i, addTag;
90     size_t length;
91     char *fullOption;
92     register CkTextTag *tagPtr;
93     CkTextIndex first, last, index1, index2;
94
95     if (argc < 3) {
96         Tcl_AppendResult(interp, "wrong # args: should be \"",
97                 argv[0], " tag option ?arg arg ...?\"", (char *) NULL);
98         return TCL_ERROR;
99     }
100     c = argv[2][0];
101     length = strlen(argv[2]);
102     if ((c == 'a') && (strncmp(argv[2], "add", length) == 0)) {
103         fullOption = "add";
104         addTag = 1;
105
106         addAndRemove:
107         if (argc < 5) {
108             Tcl_AppendResult(interp, "wrong # args: should be \"",
109                     argv[0], " tag ", fullOption,
110                     " tagName index1 ?index2 index1 index2 ...?\"",
111                     (char *) NULL);
112             return TCL_ERROR;
113         }
114         tagPtr = CkTextCreateTag(textPtr, argv[3]);
115         for (i = 4; i < argc; i += 2) {
116             if (CkTextGetIndex(interp, textPtr, argv[i], &index1) != TCL_OK) {
117                 return TCL_ERROR;
118             }
119             if (argc > (i+1)) {
120                 if (CkTextGetIndex(interp, textPtr, argv[i+1], &index2)
121                         != TCL_OK) {
122                     return TCL_ERROR;
123                 }
124                 if (CkTextIndexCmp(&index1, &index2) >= 0) {
125                     return TCL_OK;
126                 }
127             } else {
128                 index2 = index1;
129                 CkTextIndexForwChars(&index2, 1, &index2);
130             }
131     
132             if (tagPtr->affectsDisplay) {
133                 CkTextRedrawTag(textPtr, &index1, &index2, tagPtr, !addTag);
134             } else {
135                 /*
136                  * Still need to trigger enter/leave events on tags that
137                  * have changed.
138                  */
139     
140                 CkTextEventuallyRepick(textPtr);
141             }
142             CkBTreeTag(&index1, &index2, tagPtr, addTag);
143     
144             /*
145              * If the tag is "sel" then grab the selection if we're supposed
146              * to export it and don't already have it.  Also, invalidate
147              * partially-completed selection retrievals.
148              */
149     
150             if (tagPtr == textPtr->selTagPtr) {
151                 textPtr->abortSelections = 1;
152             }
153         }
154     } else if ((c == 'b') && (strncmp(argv[2], "bind", length) == 0)) {
155         if ((argc < 4) || (argc > 6)) {
156             Tcl_AppendResult(interp, "wrong # args: should be \"",
157                     argv[0], " tag bind tagName ?sequence? ?command?\"",
158                     (char *) NULL);
159             return TCL_ERROR;
160         }
161         tagPtr = CkTextCreateTag(textPtr, argv[3]);
162
163         /*
164          * Make a binding table if the widget doesn't already have
165          * one.
166          */
167
168         if (textPtr->bindingTable == NULL) {
169             textPtr->bindingTable = Ck_CreateBindingTable(interp);
170         }
171
172         if (argc == 6) {
173             int append = 0;
174             unsigned long mask;
175
176             if (argv[5][0] == 0) {
177                 return Ck_DeleteBinding(interp, textPtr->bindingTable,
178                         (ClientData) tagPtr, argv[4]);
179             }
180             if (argv[5][0] == '+') {
181                 argv[5]++;
182                 append = 1;
183             }
184             mask = Ck_CreateBinding(interp, textPtr->bindingTable,
185                     (ClientData) tagPtr, argv[4], argv[5], append);
186             if (mask != TCL_OK) {
187                 return TCL_ERROR;
188             }
189         } else if (argc == 5) {
190             char *command;
191     
192             command = Ck_GetBinding(interp, textPtr->bindingTable,
193                     (ClientData) tagPtr, argv[4]);
194             if (command == NULL) {
195                 return TCL_ERROR;
196             }
197             interp->result = command;
198         } else {
199             Ck_GetAllBindings(interp, textPtr->bindingTable,
200                     (ClientData) tagPtr);
201         }
202     } else if ((c == 'c') && (strncmp(argv[2], "cget", length) == 0)
203             && (length >= 2)) {
204         if (argc != 5) {
205             Tcl_AppendResult(interp, "wrong # args: should be \"",
206                     argv[0], " tag cget tagName option\"",
207                     (char *) NULL);
208             return TCL_ERROR;
209         }
210         tagPtr = FindTag(interp, textPtr, argv[3]);
211         if (tagPtr == NULL) {
212             return TCL_ERROR;
213         }
214         return Ck_ConfigureValue(interp, textPtr->winPtr, tagConfigSpecs,
215                 (char *) tagPtr, argv[4], 0);
216     } else if ((c == 'c') && (strncmp(argv[2], "configure", length) == 0)
217             && (length >= 2)) {
218         if (argc < 4) {
219             Tcl_AppendResult(interp, "wrong # args: should be \"",
220                     argv[0], " tag configure tagName ?option? ?value? ",
221                     "?option value ...?\"", (char *) NULL);
222             return TCL_ERROR;
223         }
224         tagPtr = CkTextCreateTag(textPtr, argv[3]);
225         if (argc == 4) {
226             return Ck_ConfigureInfo(interp, textPtr->winPtr, tagConfigSpecs,
227                     (char *) tagPtr, (char *) NULL, 0);
228         } else if (argc == 5) {
229             return Ck_ConfigureInfo(interp, textPtr->winPtr, tagConfigSpecs,
230                     (char *) tagPtr, argv[4], 0);
231         } else {
232             int result;
233
234             result = Ck_ConfigureWidget(interp, textPtr->winPtr,
235                     tagConfigSpecs, argc-4, argv+4, (char *) tagPtr, 0);
236             /*
237              * Some of the configuration options, like -underline
238              * and -justify, require additional translation (this is
239              * needed because we need to distinguish a particular value
240              * of an option from "unspecified").
241              */
242
243             if (tagPtr->justifyString != NULL) {
244                 if (Ck_GetJustify(interp, tagPtr->justifyString,
245                         &tagPtr->justify) != TCL_OK) {
246                     return TCL_ERROR;
247                 }
248             }
249             if (tagPtr->lMargin1String != NULL) {
250                 if (Ck_GetCoord(interp, textPtr->winPtr,
251                         tagPtr->lMargin1String, &tagPtr->lMargin1) != TCL_OK) {
252                     return TCL_ERROR;
253                 }
254             }
255             if (tagPtr->lMargin2String != NULL) {
256                 if (Ck_GetCoord(interp, textPtr->winPtr,
257                         tagPtr->lMargin2String, &tagPtr->lMargin2) != TCL_OK) {
258                     return TCL_ERROR;
259                 }
260             }
261             if (tagPtr->rMarginString != NULL) {
262                 if (Ck_GetCoord(interp, textPtr->winPtr,
263                         tagPtr->rMarginString, &tagPtr->rMargin) != TCL_OK) {
264                     return TCL_ERROR;
265                 }
266             }
267             if (tagPtr->tabArrayPtr != NULL) {
268                 ckfree((char *) tagPtr->tabArrayPtr);
269                 tagPtr->tabArrayPtr = NULL;
270             }
271             if (tagPtr->tabString != NULL) {
272                 tagPtr->tabArrayPtr = CkTextGetTabs(interp, textPtr->winPtr,
273                         tagPtr->tabString);
274                 if (tagPtr->tabArrayPtr == NULL) {
275                     return TCL_ERROR;
276                 }
277             }
278             if ((tagPtr->wrapMode != NULL)
279                     && (tagPtr->wrapMode != ckTextCharUid)
280                     && (tagPtr->wrapMode != ckTextNoneUid)
281                     && (tagPtr->wrapMode != ckTextWordUid)) {
282                 Tcl_AppendResult(interp, "bad wrap mode \"", tagPtr->wrapMode,
283                         "\":  must be char, none, or word", (char *) NULL);
284                 tagPtr->wrapMode = NULL;
285                 return TCL_ERROR;
286             }
287
288             /*
289              * If the "sel" tag was changed, be sure to mirror information
290              * from the tag back into the text widget record.   NOTE: we
291              * don't have to free up information in the widget record
292              * before overwriting it, because it was mirrored in the tag
293              * and hence freed when the tag field was overwritten.
294              */
295
296             if (tagPtr == textPtr->selTagPtr) {
297                 textPtr->selBg = tagPtr->bg;
298                 textPtr->selFg = tagPtr->fg;
299                 textPtr->selAttr = tagPtr->attr;
300             }
301             tagPtr->affectsDisplay = 1;
302             CkTextRedrawTag(textPtr, (CkTextIndex *) NULL,
303                     (CkTextIndex *) NULL, tagPtr, 1);
304             return result;
305         }
306     } else if ((c == 'd') && (strncmp(argv[2], "delete", length) == 0)) {
307         Tcl_HashEntry *hPtr;
308
309         if (argc < 4) {
310             Tcl_AppendResult(interp, "wrong # args: should be \"",
311                     argv[0], " tag delete tagName tagName ...\"",
312                     (char *) NULL);
313             return TCL_ERROR;
314         }
315         for (i = 3; i < argc; i++) {
316             hPtr = Tcl_FindHashEntry(&textPtr->tagTable, argv[i]);
317             if (hPtr == NULL) {
318                 continue;
319             }
320             tagPtr = (CkTextTag *) Tcl_GetHashValue(hPtr);
321             if (tagPtr == textPtr->selTagPtr) {
322                 continue;
323             }
324             if (tagPtr->affectsDisplay) {
325                 CkTextRedrawTag(textPtr, (CkTextIndex *) NULL,
326                         (CkTextIndex *) NULL, tagPtr, 1);
327             }
328 #if CK_USE_UTF
329             CkBTreeTag(CkTextMakeByteIndex(textPtr->tree, 0, 0, &first),
330                     CkTextMakeByteIndex(textPtr->tree,
331                             CkBTreeNumLines(textPtr->tree), 0, &last),
332                     tagPtr, 0);
333 #else
334             CkBTreeTag(CkTextMakeIndex(textPtr->tree, 0, 0, &first),
335                     CkTextMakeIndex(textPtr->tree,
336                             CkBTreeNumLines(textPtr->tree), 0, &last),
337                     tagPtr, 0);
338 #endif
339             Tcl_DeleteHashEntry(hPtr);
340             if (textPtr->bindingTable != NULL) {
341                 Ck_DeleteAllBindings(textPtr->bindingTable,
342                         (ClientData) tagPtr);
343             }
344         
345             /*
346              * Update the tag priorities to reflect the deletion of this tag.
347              */
348
349             ChangeTagPriority(textPtr, tagPtr, textPtr->numTags-1);
350             textPtr->numTags -= 1;
351             CkTextFreeTag(textPtr, tagPtr);
352         }
353     } else if ((c == 'l') && (strncmp(argv[2], "lower", length) == 0)) {
354         CkTextTag *tagPtr2;
355         int prio;
356
357         if ((argc != 4) && (argc != 5)) {
358             Tcl_AppendResult(interp, "wrong # args: should be \"",
359                     argv[0], " tag lower tagName ?belowThis?\"",
360                     (char *) NULL);
361             return TCL_ERROR;
362         }
363         tagPtr = FindTag(interp, textPtr, argv[3]);
364         if (tagPtr == NULL) {
365             return TCL_ERROR;
366         }
367         if (argc == 5) {
368             tagPtr2 = FindTag(interp, textPtr, argv[4]);
369             if (tagPtr2 == NULL) {
370                 return TCL_ERROR;
371             }
372             if (tagPtr->priority < tagPtr2->priority) {
373                 prio = tagPtr2->priority - 1;
374             } else {
375                 prio = tagPtr2->priority;
376             }
377         } else {
378             prio = 0;
379         }
380         ChangeTagPriority(textPtr, tagPtr, prio);
381         CkTextRedrawTag(textPtr, (CkTextIndex *) NULL, (CkTextIndex *) NULL,
382                 tagPtr, 1);
383     } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)
384             && (length >= 2)) {
385         CkTextTag **arrayPtr;
386         int arraySize;
387
388         if ((argc != 3) && (argc != 4)) {
389             Tcl_AppendResult(interp, "wrong # args: should be \"",
390                     argv[0], " tag names ?index?\"",
391                     (char *) NULL);
392             return TCL_ERROR;
393         }
394         if (argc == 3) {
395             Tcl_HashSearch search;
396             Tcl_HashEntry *hPtr;
397
398             arrayPtr = (CkTextTag **) ckalloc((unsigned)
399                     (textPtr->numTags * sizeof(CkTextTag *)));
400             for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
401                     hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
402                 arrayPtr[i] = (CkTextTag *) Tcl_GetHashValue(hPtr);
403             }
404             arraySize = textPtr->numTags;
405         } else {
406             if (CkTextGetIndex(interp, textPtr, argv[3], &index1)
407                     != TCL_OK) {
408                 return TCL_ERROR;
409             }
410             arrayPtr = CkBTreeGetTags(&index1, &arraySize);
411             if (arrayPtr == NULL) {
412                 return TCL_OK;
413             }
414         }
415         SortTags(arraySize, arrayPtr);
416         for (i = 0; i < arraySize; i++) {
417             tagPtr = arrayPtr[i];
418             Tcl_AppendElement(interp, tagPtr->name);
419         }
420         ckfree((char *) arrayPtr);
421     } else if ((c == 'n') && (strncmp(argv[2], "nextrange", length) == 0)
422             && (length >= 2)) {
423         CkTextSearch tSearch;
424         char position[TK_POS_CHARS];
425
426         if ((argc != 5) && (argc != 6)) {
427             Tcl_AppendResult(interp, "wrong # args: should be \"",
428                     argv[0], " tag nextrange tagName index1 ?index2?\"",
429                     (char *) NULL);
430             return TCL_ERROR;
431         }
432         tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]);
433         if (tagPtr == NULL) {
434             return TCL_OK;
435         }
436         if (CkTextGetIndex(interp, textPtr, argv[4], &index1) != TCL_OK) {
437             return TCL_ERROR;
438         }
439 #if CK_USE_UTF
440         CkTextMakeByteIndex(textPtr->tree, CkBTreeNumLines(textPtr->tree),
441                 0, &last);
442 #else
443         CkTextMakeIndex(textPtr->tree, CkBTreeNumLines(textPtr->tree),
444                 0, &last);
445 #endif
446         if (argc == 5) {
447             index2 = last;
448         } else if (CkTextGetIndex(interp, textPtr, argv[5], &index2)
449                 != TCL_OK) {
450             return TCL_ERROR;
451         }
452
453         /*
454          * The search below is a bit tricky.  Rather than use the B-tree
455          * facilities to stop the search at index2, let it search up
456          * until the end of the file but check for a position past index2
457          * ourselves.  The reason for doing it this way is that we only
458          * care whether the *start* of the range is before index2;  once
459          * we find the start, we don't want CkBTreeNextTag to abort the
460          * search because the end of the range is after index2.
461          */
462
463         CkBTreeStartSearch(&index1, &last, tagPtr, &tSearch);
464         if (CkBTreeCharTagged(&index1, tagPtr)) {
465             CkTextSegment *segPtr;
466             int offset;
467
468             /*
469              * The first character is tagged.  See if there is an
470              * on-toggle just before the character.  If not, then
471              * skip to the end of this tagged range.
472              */
473
474             for (segPtr = index1.linePtr->segPtr, offset = index1.charIndex; 
475                     offset >= 0;
476                     offset -= segPtr->size, segPtr = segPtr->nextPtr) {
477                 if ((offset == 0) && (segPtr->typePtr == &ckTextToggleOnType)
478                         && (segPtr->body.toggle.tagPtr == tagPtr)) {
479                     goto gotStart;
480                 }
481             }
482             if (!CkBTreeNextTag(&tSearch)) {
483                  return TCL_OK;
484             }
485         }
486
487         /*
488          * Find the start of the tagged range.
489          */
490
491         if (!CkBTreeNextTag(&tSearch)) {
492             return TCL_OK;
493         }
494         gotStart:
495         if (CkTextIndexCmp(&tSearch.curIndex, &index2) >= 0) {
496             return TCL_OK;
497         }
498         CkTextPrintIndex(&tSearch.curIndex, position);
499         Tcl_AppendElement(interp, position);
500         CkBTreeNextTag(&tSearch);
501         CkTextPrintIndex(&tSearch.curIndex, position);
502         Tcl_AppendElement(interp, position);
503     } else if ((c == 'r') && (strncmp(argv[2], "raise", length) == 0)
504             && (length >= 3)) {
505         CkTextTag *tagPtr2;
506         int prio;
507
508         if ((argc != 4) && (argc != 5)) {
509             Tcl_AppendResult(interp, "wrong # args: should be \"",
510                     argv[0], " tag raise tagName ?aboveThis?\"",
511                     (char *) NULL);
512             return TCL_ERROR;
513         }
514         tagPtr = FindTag(interp, textPtr, argv[3]);
515         if (tagPtr == NULL) {
516             return TCL_ERROR;
517         }
518         if (argc == 5) {
519             tagPtr2 = FindTag(interp, textPtr, argv[4]);
520             if (tagPtr2 == NULL) {
521                 return TCL_ERROR;
522             }
523             if (tagPtr->priority <= tagPtr2->priority) {
524                 prio = tagPtr2->priority;
525             } else {
526                 prio = tagPtr2->priority + 1;
527             }
528         } else {
529             prio = textPtr->numTags-1;
530         }
531         ChangeTagPriority(textPtr, tagPtr, prio);
532         CkTextRedrawTag(textPtr, (CkTextIndex *) NULL, (CkTextIndex *) NULL,
533                 tagPtr, 1);
534     } else if ((c == 'r') && (strncmp(argv[2], "ranges", length) == 0)
535             && (length >= 3)) {
536         CkTextSearch tSearch;
537         char position[TK_POS_CHARS];
538
539         if (argc != 4) {
540             Tcl_AppendResult(interp, "wrong # args: should be \"",
541                     argv[0], " tag ranges tagName\"", (char *) NULL);
542             return TCL_ERROR;
543         }
544         tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]);
545         if (tagPtr == NULL) {
546             return TCL_OK;
547         }
548 #if CK_USE_UTF
549         CkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
550         CkTextMakeByteIndex(textPtr->tree, CkBTreeNumLines(textPtr->tree),
551                 0, &last);
552 #else
553         CkTextMakeIndex(textPtr->tree, 0, 0, &first);
554         CkTextMakeIndex(textPtr->tree, CkBTreeNumLines(textPtr->tree),
555                 0, &last);
556 #endif
557         CkBTreeStartSearch(&first, &last, tagPtr, &tSearch);
558         if (CkBTreeCharTagged(&first, tagPtr)) {
559             CkTextPrintIndex(&first, position);
560             Tcl_AppendElement(interp, position);
561         }
562         while (CkBTreeNextTag(&tSearch)) {
563             CkTextPrintIndex(&tSearch.curIndex, position);
564             Tcl_AppendElement(interp, position);
565         }
566     } else if ((c == 'r') && (strncmp(argv[2], "remove", length) == 0)
567             && (length >= 2)) {
568         fullOption = "remove";
569         addTag = 0;
570         goto addAndRemove;
571     } else {
572         Tcl_AppendResult(interp, "bad tag option \"", argv[2],
573                 "\":  must be add, bind, cget, configure, delete, lower, ",
574                 "names, nextrange, raise, ranges, or remove",
575                 (char *) NULL);
576         return TCL_ERROR;
577     }
578     return TCL_OK;
579 }
580 \f
581 /*
582  *----------------------------------------------------------------------
583  *
584  * CkTextCreateTag --
585  *
586  *      Find the record describing a tag within a given text widget,
587  *      creating a new record if one doesn't already exist.
588  *
589  * Results:
590  *      The return value is a pointer to the CkTextTag record for tagName.
591  *
592  * Side effects:
593  *      A new tag record is created if there isn't one already defined
594  *      for tagName.
595  *
596  *----------------------------------------------------------------------
597  */
598
599 CkTextTag *
600 CkTextCreateTag(textPtr, tagName)
601     CkText *textPtr;            /* Widget in which tag is being used. */
602     char *tagName;              /* Name of desired tag. */
603 {
604     register CkTextTag *tagPtr;
605     Tcl_HashEntry *hPtr;
606     int new;
607
608     hPtr = Tcl_CreateHashEntry(&textPtr->tagTable, tagName, &new);
609     if (!new) {
610         return (CkTextTag *) Tcl_GetHashValue(hPtr);
611     }
612
613     /*
614      * No existing entry.  Create a new one, initialize it, and add a
615      * pointer to it to the hash table entry.
616      */
617
618     tagPtr = (CkTextTag *) ckalloc(sizeof(CkTextTag));
619     tagPtr->name = Tcl_GetHashKey(&textPtr->tagTable, hPtr);
620     tagPtr->priority = textPtr->numTags;
621     tagPtr->bg = -1;
622     tagPtr->fg = -1;
623     tagPtr->attr = -1;
624     tagPtr->justifyString = NULL;
625     tagPtr->justify = CK_JUSTIFY_LEFT;
626     tagPtr->lMargin1String = NULL;
627     tagPtr->lMargin1 = 0;
628     tagPtr->lMargin2String = NULL;
629     tagPtr->lMargin2 = 0;
630     tagPtr->rMarginString = NULL;
631     tagPtr->rMargin = 0;
632     tagPtr->tabString = NULL;
633     tagPtr->tabArrayPtr = NULL;
634     tagPtr->wrapMode = NULL;
635     tagPtr->affectsDisplay = 0;
636     textPtr->numTags++;
637     Tcl_SetHashValue(hPtr, tagPtr);
638     return tagPtr;
639 }
640 \f
641 /*
642  *----------------------------------------------------------------------
643  *
644  * FindTag --
645  *
646  *      See if tag is defined for a given widget.
647  *
648  * Results:
649  *      If tagName is defined in textPtr, a pointer to its CkTextTag
650  *      structure is returned.  Otherwise NULL is returned and an
651  *      error message is recorded in interp->result unless interp
652  *      is NULL.
653  *
654  * Side effects:
655  *      None.
656  *
657  *----------------------------------------------------------------------
658  */
659
660 static CkTextTag *
661 FindTag(interp, textPtr, tagName)
662     Tcl_Interp *interp;         /* Interpreter to use for error message;
663                                  * if NULL, then don't record an error
664                                  * message. */
665     CkText *textPtr;            /* Widget in which tag is being used. */
666     char *tagName;              /* Name of desired tag. */
667 {
668     Tcl_HashEntry *hPtr;
669
670     hPtr = Tcl_FindHashEntry(&textPtr->tagTable, tagName);
671     if (hPtr != NULL) {
672         return (CkTextTag *) Tcl_GetHashValue(hPtr);
673     }
674     if (interp != NULL) {
675         Tcl_AppendResult(interp, "tag \"", tagName,
676                 "\" isn't defined in text widget", (char *) NULL);
677     }
678     return NULL;
679 }
680 \f
681 /*
682  *----------------------------------------------------------------------
683  *
684  * CkTextFreeTag --
685  *
686  *      This procedure is called when a tag is deleted to free up the
687  *      memory and other resources associated with the tag.
688  *
689  * Results:
690  *      None.
691  *
692  * Side effects:
693  *      Memory and other resources are freed.
694  *
695  *----------------------------------------------------------------------
696  */
697
698 void
699 CkTextFreeTag(textPtr, tagPtr)
700     CkText *textPtr;                    /* Info about overall widget. */
701     register CkTextTag *tagPtr;         /* Tag being deleted. */
702 {
703     if (tagPtr->justifyString != NULL) {
704         ckfree(tagPtr->justifyString);
705     }
706     if (tagPtr->lMargin1String != NULL) {
707         ckfree(tagPtr->lMargin1String);
708     }
709     if (tagPtr->lMargin2String != NULL) {
710         ckfree(tagPtr->lMargin2String);
711     }
712     if (tagPtr->rMarginString != NULL) {
713         ckfree(tagPtr->rMarginString);
714     }
715     if (tagPtr->tabString != NULL) {
716         ckfree(tagPtr->tabString);
717     }
718     if (tagPtr->tabArrayPtr != NULL) {
719         ckfree((char *) tagPtr->tabArrayPtr);
720     }
721     ckfree((char *) tagPtr);
722 }
723 \f
724 /*
725  *----------------------------------------------------------------------
726  *
727  * SortTags --
728  *
729  *      This procedure sorts an array of tag pointers in increasing
730  *      order of priority, optimizing for the common case where the
731  *      array is small.
732  *
733  * Results:
734  *      None.
735  *
736  * Side effects:
737  *      None.
738  *
739  *----------------------------------------------------------------------
740  */
741
742 static void
743 SortTags(numTags, tagArrayPtr)
744     int numTags;                /* Number of tag pointers at *tagArrayPtr. */
745     CkTextTag **tagArrayPtr;    /* Pointer to array of pointers. */
746 {
747     int i, j, prio;
748     register CkTextTag **tagPtrPtr;
749     CkTextTag **maxPtrPtr, *tmp;
750
751     if (numTags < 2) {
752         return;
753     }
754     if (numTags < 20) {
755         for (i = numTags-1; i > 0; i--, tagArrayPtr++) {
756             maxPtrPtr = tagPtrPtr = tagArrayPtr;
757             prio = tagPtrPtr[0]->priority;
758             for (j = i, tagPtrPtr++; j > 0; j--, tagPtrPtr++) {
759                 if (tagPtrPtr[0]->priority < prio) {
760                     prio = tagPtrPtr[0]->priority;
761                     maxPtrPtr = tagPtrPtr;
762                 }
763             }
764             tmp = *maxPtrPtr;
765             *maxPtrPtr = *tagArrayPtr;
766             *tagArrayPtr = tmp;
767         }
768     } else {
769         qsort((VOID *) tagArrayPtr, (unsigned) numTags, sizeof (CkTextTag *),
770                     TagSortProc);
771     }
772 }
773 \f
774 /*
775  *----------------------------------------------------------------------
776  *
777  * TagSortProc --
778  *
779  *      This procedure is called by qsort when sorting an array of
780  *      tags in priority order.
781  *
782  * Results:
783  *      The return value is -1 if the first argument should be before
784  *      the second element (i.e. it has lower priority), 0 if it's
785  *      equivalent (this should never happen!), and 1 if it should be
786  *      after the second element.
787  *
788  * Side effects:
789  *      None.
790  *
791  *----------------------------------------------------------------------
792  */
793
794 static int
795 TagSortProc(first, second)
796     CONST VOID *first, *second;         /* Elements to be compared. */
797 {
798     CkTextTag *tagPtr1, *tagPtr2;
799
800     tagPtr1 = * (CkTextTag **) first;
801     tagPtr2 = * (CkTextTag **) second;
802     return tagPtr1->priority - tagPtr2->priority;
803 }
804 \f
805 /*
806  *----------------------------------------------------------------------
807  *
808  * ChangeTagPriority --
809  *
810  *      This procedure changes the priority of a tag by modifying
811  *      its priority and the priorities of other tags that are affected
812  *      by the change.
813  *
814  * Results:
815  *      None.
816  *
817  * Side effects:
818  *      Priorities may be changed for some or all of the tags in
819  *      textPtr.  The tags will be arranged so that there is exactly
820  *      one tag at each priority level between 0 and textPtr->numTags-1,
821  *      with tagPtr at priority "prio".
822  *
823  *----------------------------------------------------------------------
824  */
825
826 static void
827 ChangeTagPriority(textPtr, tagPtr, prio)
828     CkText *textPtr;                    /* Information about text widget. */
829     CkTextTag *tagPtr;                  /* Tag whose priority is to be
830                                          * changed. */
831     int prio;                           /* New priority for tag. */
832 {
833     int low, high, delta;
834     register CkTextTag *tagPtr2;
835     Tcl_HashEntry *hPtr;
836     Tcl_HashSearch search;
837
838     if (prio < 0) {
839         prio = 0;
840     }
841     if (prio >= textPtr->numTags) {
842         prio = textPtr->numTags-1;
843     }
844     if (prio == tagPtr->priority) {
845         return;
846     } else if (prio < tagPtr->priority) {
847         low = prio;
848         high = tagPtr->priority-1;
849         delta = 1;
850     } else {
851         low = tagPtr->priority+1;
852         high = prio;
853         delta = -1;
854     }
855     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
856             hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
857         tagPtr2 = (CkTextTag *) Tcl_GetHashValue(hPtr);
858         if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) {
859             tagPtr2->priority += delta;
860         }
861     }
862     tagPtr->priority = prio;
863 }
864 \f
865 /*
866  *--------------------------------------------------------------
867  *
868  * CkTextBindProc --
869  *
870  *      This procedure is invoked by the Ck dispatcher to handle
871  *      events associated with bindings on items.
872  *
873  * Results:
874  *      None.
875  *
876  * Side effects:
877  *      Depends on the command invoked as part of the binding
878  *      (if there was any).
879  *
880  *--------------------------------------------------------------
881  */
882
883 void
884 CkTextBindProc(clientData, eventPtr)
885     ClientData clientData;              /* Pointer to canvas structure. */
886     CkEvent *eventPtr;                  /* Pointer to X event that just
887                                          * happened. */
888 {
889     CkText *textPtr = (CkText *) clientData;
890 }
891 \f
892 /*
893  *--------------------------------------------------------------
894  *
895  * CkTextPickCurrent --
896  *
897  *      Find the character containing the coordinates in an event
898  *      and place the "current" mark on that character.  If the
899  *      "current" mark has moved then generate a fake leave event
900  *      on the old current character and a fake enter event on the new
901  *      current character.
902  *
903  * Results:
904  *      None.
905  *
906  * Side effects:
907  *      The current mark for textPtr may change.  If it does,
908  *      then the commands associated with character entry and leave
909  *      could do just about anything.
910  *
911  *--------------------------------------------------------------
912  */
913
914 void
915 CkTextPickCurrent(textPtr, eventPtr)
916     register CkText *textPtr;           /* Text widget in which to select
917                                          * current character. */
918     CkEvent *eventPtr;                  /* Event describing location of
919                                          * mouse cursor.  Must be EnterWindow,
920                                          * LeaveWindow, ButtonRelease, or
921                                          * MotionNotify. */
922 {
923 }