4  *      This module implements the "tag" subcommand of the widget command 
   5  *      for text widgets, plus most of the other high-level functions 
   8  * Copyright 1992 Regents of the University of California. 
   9  * Permission to use, copy, modify, and distribute this 
  10  * software and its documentation for any purpose and without 
  11  * fee is hereby granted, provided that the above copyright 
  12  * notice appear in all copies.  The University of California 
  13  * makes no representations about the suitability of this 
  14  * software for any purpose.  It is provided "as is" without 
  15  * express or implied warranty. 
  19 static char rcsid
[] = "$Header: /user6/ouster/wish/RCS/tkTextTag.c,v 1.3 92/07/28 15:38:59 ouster Exp $ SPRITE (Berkeley)"; 
  28  * Information used for parsing tag configuration information: 
  31 static Tk_ConfigSpec tagConfigSpecs
[] = { 
  32     {TK_CONFIG_BORDER
, "-background", (char *) NULL
, (char *) NULL
, 
  33         (char *) NULL
, Tk_Offset(TkTextTag
, border
), TK_CONFIG_NULL_OK
}, 
  34     {TK_CONFIG_BITMAP
, "-bgstipple", (char *) NULL
, (char *) NULL
, 
  35         (char *) NULL
, Tk_Offset(TkTextTag
, bgStipple
), TK_CONFIG_NULL_OK
}, 
  36     {TK_CONFIG_PIXELS
, "-borderwidth", (char *) NULL
, (char *) NULL
, 
  37         "0", Tk_Offset(TkTextTag
, borderWidth
), TK_CONFIG_DONT_SET_DEFAULT
}, 
  38     {TK_CONFIG_BITMAP
, "-fgstipple", (char *) NULL
, (char *) NULL
, 
  39         (char *) NULL
, Tk_Offset(TkTextTag
, fgStipple
), TK_CONFIG_NULL_OK
}, 
  40     {TK_CONFIG_FONT
, "-font", (char *) NULL
, (char *) NULL
, 
  41         (char *) NULL
, Tk_Offset(TkTextTag
, fontPtr
), TK_CONFIG_NULL_OK
}, 
  42     {TK_CONFIG_COLOR
, "-foreground", (char *) NULL
, (char *) NULL
, 
  43         (char *) NULL
, Tk_Offset(TkTextTag
, fgColor
), TK_CONFIG_NULL_OK
}, 
  44     {TK_CONFIG_RELIEF
, "-relief", (char *) NULL
, (char *) NULL
, 
  45         "flat", Tk_Offset(TkTextTag
, relief
), TK_CONFIG_DONT_SET_DEFAULT
}, 
  46     {TK_CONFIG_BOOLEAN
, "-underline", (char *) NULL
, (char *) NULL
, 
  47         "false", Tk_Offset(TkTextTag
, underline
), TK_CONFIG_DONT_SET_DEFAULT
}, 
  48     {TK_CONFIG_END
, (char *) NULL
, (char *) NULL
, (char *) NULL
, 
  54  * The following definition specifies the maximum number of characters 
  55  * needed in a string to hold a position specifier. 
  61  * Forward declarations for procedures defined later in this file: 
  64 static void             ChangeTagPriority 
_ANSI_ARGS_((TkText 
*textPtr
, 
  65                             TkTextTag 
*tagPtr
, int prio
)); 
  66 static TkTextTag 
*      FindTag 
_ANSI_ARGS_((Tcl_Interp 
*interp
, 
  67                             TkText 
*textPtr
, char *tagName
)); 
  68 static void             SortTags 
_ANSI_ARGS_((int numTags
, 
  69                             TkTextTag 
**tagArrayPtr
)); 
  70 static int              TagSortProc 
_ANSI_ARGS_((CONST VOID 
*first
, 
  72 static void             TextDoEvent 
_ANSI_ARGS_((TkText 
*textPtr
, 
  76  *-------------------------------------------------------------- 
  80  *      This procedure is invoked to process the "tag" options of 
  81  *      the widget command for text widgets. See the user documentation 
  82  *      for details on what it does. 
  85  *      A standard Tcl result. 
  88  *      See the user documentation. 
  90  *-------------------------------------------------------------- 
  94 TkTextTagCmd(textPtr
, interp
, argc
, argv
) 
  95     register TkText 
*textPtr
;   /* Information about text widget. */ 
  96     Tcl_Interp 
*interp
;         /* Current interpreter. */ 
  97     int argc
;                   /* Number of arguments. */ 
  98     char **argv
;                /* Argument strings.  Someone else has already 
  99                                  * parsed this command enough to know that 
 100                                  * argv[1] is "tag". */ 
 102     int length
, line1
, ch1
, line2
, ch2
, i
, addTag
; 
 105     register TkTextTag 
*tagPtr
; 
 108         Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 109                 argv
[0], " tag option ?arg arg ...?\"", (char *) NULL
); 
 113     length 
= strlen(argv
[2]); 
 114     if ((c 
== 'a') && (strncmp(argv
[2], "add", length
) == 0)) { 
 119         if ((argc 
!= 5) && (argc 
!= 6)) { 
 120             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 121                     argv
[0], " tag ", fullOption
, " tagName index1 ?index2?\"", 
 125         tagPtr 
= TkTextCreateTag(textPtr
, argv
[3]); 
 126         if (TkTextGetIndex(interp
, textPtr
, argv
[4], &line1
, &ch1
) != TCL_OK
) { 
 130             if (TkTextGetIndex(interp
, textPtr
, argv
[5], &line2
, &ch2
) 
 138         if (TK_TAG_AFFECTS_DISPLAY(tagPtr
)) { 
 139             TkTextRedrawTag(textPtr
, line1
, ch1
, line2
, ch2
, tagPtr
, !addTag
); 
 141         TkBTreeTag(textPtr
->tree
, line1
, ch1
, line2
, ch2
, tagPtr
, addTag
); 
 144          * If the tag is "sel" then grab the selection if we're supposed 
 145          * to export it and don't already have it.  Also, invalidate 
 146          * partially-completed selection retrievals. 
 149         if (tagPtr 
== textPtr
->selTagPtr
) { 
 150             if (addTag 
&& textPtr
->exportSelection
 
 151                     && !(textPtr
->flags 
& GOT_SELECTION
)) { 
 152                 Tk_OwnSelection(textPtr
->tkwin
, TkTextLostSelection
, 
 153                         (ClientData
) textPtr
); 
 154                 textPtr
->flags 
|= GOT_SELECTION
; 
 156             textPtr
->selOffset 
= -1; 
 158     } else if ((c 
== 'b') && (strncmp(argv
[2], "bind", length
) == 0)) { 
 159         if ((argc 
< 4) || (argc 
> 6)) { 
 160             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 161                     argv
[0], " tag bind tagName ?sequence? ?command?\"", 
 165         tagPtr 
= TkTextCreateTag(textPtr
, argv
[3]); 
 168          * Make a binding table if the widget doesn't already have 
 172         if (textPtr
->bindingTable 
== NULL
) { 
 173             textPtr
->bindingTable 
= Tk_CreateBindingTable(interp
); 
 180             if (argv
[5][0] == 0) { 
 181                 return Tk_DeleteBinding(interp
, textPtr
->bindingTable
, 
 182                         (ClientData
) tagPtr
, argv
[4]); 
 184             if (argv
[5][0] == '+') { 
 188             mask 
= Tk_CreateBinding(interp
, textPtr
->bindingTable
, 
 189                     (ClientData
) tagPtr
, argv
[4], argv
[5], append
); 
 193             if (mask 
& ~(ButtonMotionMask
|Button1MotionMask
|Button2MotionMask
 
 194                     |Button3MotionMask
|Button4MotionMask
|Button5MotionMask
 
 195                     |ButtonPressMask
|ButtonReleaseMask
|EnterWindowMask
 
 196                     |LeaveWindowMask
|KeyPressMask
|KeyReleaseMask
 
 197                     |PointerMotionMask
)) { 
 198                 Tk_DeleteBinding(interp
, textPtr
->bindingTable
, 
 199                         (ClientData
) tagPtr
, argv
[4]); 
 200                 Tcl_ResetResult(interp
); 
 201                 Tcl_AppendResult(interp
, "requested illegal events; ", 
 202                         "only key, button, motion, and enter/leave ", 
 203                         "events may be used", (char *) NULL
); 
 206         } else if (argc 
== 5) { 
 209             command 
= Tk_GetBinding(interp
, textPtr
->bindingTable
, 
 210                     (ClientData
) tagPtr
, argv
[4]); 
 211             if (command 
== NULL
) { 
 214             interp
->result 
= command
; 
 216             Tk_GetAllBindings(interp
, textPtr
->bindingTable
, 
 217                     (ClientData
) tagPtr
); 
 219     } else if ((c 
== 'c') && (strncmp(argv
[2], "configure", length
) == 0)) { 
 221             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 222                     argv
[0], " tag configure tagName ?option? ?value? ", 
 223                     "?option value ...?\"", (char *) NULL
); 
 226         tagPtr 
= TkTextCreateTag(textPtr
, argv
[3]); 
 228             return Tk_ConfigureInfo(interp
, textPtr
->tkwin
, tagConfigSpecs
, 
 229                     (char *) tagPtr
, (char *) NULL
, 0); 
 230         } else if (argc 
== 5) { 
 231             return Tk_ConfigureInfo(interp
, textPtr
->tkwin
, tagConfigSpecs
, 
 232                     (char *) tagPtr
, argv
[4], 0); 
 236             result 
= Tk_ConfigureWidget(interp
, textPtr
->tkwin
, tagConfigSpecs
, 
 237                     argc
-4, argv
+4, (char *) tagPtr
, 0); 
 239              * If the "sel" tag was changed, be sure to mirror information 
 240              * from the tag back into the text widget record.   NOTE: we 
 241              * don't have to free up information in the widget record 
 242              * before overwriting it, because it was mirrored in the tag 
 243              * and hence freed when the tag field was overwritten. 
 246             if (tagPtr 
== textPtr
->selTagPtr
) { 
 247                 textPtr
->selBorder 
= tagPtr
->border
; 
 248                 textPtr
->selBorderWidth 
= tagPtr
->borderWidth
; 
 249                 textPtr
->selFgColorPtr 
= tagPtr
->fgColor
; 
 251             TkTextRedrawTag(textPtr
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 255     } else if ((c 
== 'd') && (strncmp(argv
[2], "delete", length
) == 0)) { 
 259             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 260                     argv
[0], " tag delete tagName tagName ...\"", 
 264         for (i 
= 3; i 
< argc
; i
++) { 
 265             hPtr 
= Tcl_FindHashEntry(&textPtr
->tagTable
, argv
[i
]); 
 269             tagPtr 
= (TkTextTag 
*) Tcl_GetHashValue(hPtr
); 
 270             if (tagPtr 
== textPtr
->selTagPtr
) { 
 271                 interp
->result 
= "can't delete selection tag"; 
 274             if (TK_TAG_AFFECTS_DISPLAY(tagPtr
)) { 
 275                 TkTextRedrawTag(textPtr
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 278             TkBTreeTag(textPtr
->tree
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 280             Tcl_DeleteHashEntry(hPtr
); 
 281             if (textPtr
->bindingTable 
!= NULL
) { 
 282                 Tk_DeleteAllBindings(textPtr
->bindingTable
, 
 283                         (ClientData
) tagPtr
); 
 287              * Update the tag priorities to reflect the deletion of this tag. 
 290             ChangeTagPriority(textPtr
, tagPtr
, textPtr
->numTags
-1); 
 291             textPtr
->numTags 
-= 1; 
 292             TkTextFreeTag(tagPtr
); 
 294     } else if ((c 
== 'l') && (strncmp(argv
[2], "lower", length
) == 0)) { 
 298         if ((argc 
!= 4) && (argc 
!= 5)) { 
 299             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 300                     argv
[0], " tag lower tagName ?belowThis?\"", 
 304             tagPtr 
= FindTag(interp
, textPtr
, argv
[3]); 
 305         if (tagPtr 
== NULL
) { 
 309             tagPtr2 
= FindTag(interp
, textPtr
, argv
[4]); 
 310             if (tagPtr2 
== NULL
) { 
 313             if (tagPtr
->priority 
< tagPtr2
->priority
) { 
 314                 prio 
= tagPtr2
->priority 
- 1; 
 316                 prio 
= tagPtr2
->priority
; 
 321         ChangeTagPriority(textPtr
, tagPtr
, prio
); 
 322         TkTextRedrawTag(textPtr
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 324     } else if ((c 
== 'n') && (strncmp(argv
[2], "names", length
) == 0) 
 326         TkTextTag 
**arrayPtr
; 
 330         if ((argc 
!= 3) && (argc 
!= 4)) { 
 331             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 332                     argv
[0], " tag names ?index?\"", 
 337             Tcl_HashSearch search
; 
 340             arrayPtr 
= (TkTextTag 
**) ckalloc((unsigned) 
 341                     (textPtr
->numTags 
* sizeof(TkTextTag 
*))); 
 342             for (i 
= 0, hPtr 
= Tcl_FirstHashEntry(&textPtr
->tagTable
, &search
); 
 343                     hPtr 
!= NULL
; i
++, hPtr 
= Tcl_NextHashEntry(&search
)) { 
 344                 arrayPtr
[i
] = (TkTextTag 
*) Tcl_GetHashValue(hPtr
); 
 346             arraySize 
= textPtr
->numTags
; 
 348             if (TkTextGetIndex(interp
, textPtr
, argv
[3], &line1
, &ch1
) 
 352             linePtr 
= TkBTreeFindLine(textPtr
->tree
, line1
); 
 353             if (linePtr 
== NULL
) { 
 356             arrayPtr 
= TkBTreeGetTags(textPtr
->tree
, linePtr
, ch1
, &arraySize
); 
 357             if (arrayPtr 
== NULL
) { 
 361         SortTags(arraySize
, arrayPtr
); 
 362         for (i 
= 0; i 
< arraySize
; i
++) { 
 363             tagPtr 
= arrayPtr
[i
]; 
 364             Tcl_AppendElement(interp
, tagPtr
->name
, 0); 
 366         ckfree((char *) arrayPtr
); 
 367     } else if ((c 
== 'n') && (strncmp(argv
[2], "nextrange", length
) == 0) 
 369         TkTextSearch tSearch
; 
 370         char position
[POS_CHARS
]; 
 372         if ((argc 
!= 5) && (argc 
!= 6)) { 
 373             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 374                     argv
[0], " tag nextrange tagName index1 ?index2?\"", 
 378         tagPtr 
= FindTag((Tcl_Interp 
*) NULL
, textPtr
, argv
[3]); 
 379         if (tagPtr 
== NULL
) { 
 382         if (TkTextGetIndex(interp
, textPtr
, argv
[4], &line1
, &ch1
) != TCL_OK
) { 
 386             line2 
= TkBTreeNumLines(textPtr
->tree
); 
 388         } else if (TkTextGetIndex(interp
, textPtr
, argv
[5], &line2
, &ch2
) 
 394          * The search below is a bit tricky.  Rather than use the B-tree 
 395          * facilities to stop the search at line2.ch2, let it search up 
 396          * until the end of the file but check for a position past line2.ch2 
 397          * ourselves.  The reason for doing it this way is that we only 
 398          * care whether the *start* of the range is before line2.ch2;  once 
 399          * we find the start, we don't want TkBTreeNextTag to abort the 
 400          * search because the end of the range is after line2.ch2. 
 403         TkBTreeStartSearch(textPtr
->tree
, line1
, ch1
, 
 404                 TkBTreeNumLines(textPtr
->tree
), 0, tagPtr
, &tSearch
); 
 405         if (!TkBTreeNextTag(&tSearch
)) { 
 408         if (!TkBTreeCharTagged(tSearch
.linePtr
, tSearch
.ch1
, tagPtr
)) { 
 409             if (!TkBTreeNextTag(&tSearch
)) { 
 413         if ((tSearch
.line1 
> line2
) || ((tSearch
.line1 
== line2
) 
 414                 && (tSearch
.ch1 
>= ch2
))) { 
 417         TkTextPrintIndex(tSearch
.line1
, tSearch
.ch1
, position
); 
 418         Tcl_AppendElement(interp
, position
, 0); 
 419         TkBTreeNextTag(&tSearch
); 
 420         TkTextPrintIndex(tSearch
.line1
, tSearch
.ch1
, position
); 
 421         Tcl_AppendElement(interp
, position
, 0); 
 422     } else if ((c 
== 'r') && (strncmp(argv
[2], "raise", length
) == 0) 
 427         if ((argc 
!= 4) && (argc 
!= 5)) { 
 428             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 429                     argv
[0], " tag raise tagName ?aboveThis?\"", 
 433         tagPtr 
= FindTag(interp
, textPtr
, argv
[3]); 
 434         if (tagPtr 
== NULL
) { 
 438             tagPtr2 
= FindTag(interp
, textPtr
, argv
[4]); 
 439             if (tagPtr2 
== NULL
) { 
 442             if (tagPtr
->priority 
<= tagPtr2
->priority
) { 
 443                 prio 
= tagPtr2
->priority
; 
 445                 prio 
= tagPtr2
->priority 
+ 1; 
 448             prio 
= textPtr
->numTags
-1; 
 450         ChangeTagPriority(textPtr
, tagPtr
, prio
); 
 451         TkTextRedrawTag(textPtr
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 453     } else if ((c 
== 'r') && (strncmp(argv
[2], "ranges", length
) == 0) 
 455         TkTextSearch tSearch
; 
 456         char position
[POS_CHARS
]; 
 459             Tcl_AppendResult(interp
, "wrong # args: should be \"", 
 460                     argv
[0], " tag ranges tagName\"", (char *) NULL
); 
 463         tagPtr 
= FindTag((Tcl_Interp 
*) NULL
, textPtr
, argv
[3]); 
 464         if (tagPtr 
== NULL
) { 
 467         TkBTreeStartSearch(textPtr
->tree
, 0, 0, TkBTreeNumLines(textPtr
->tree
), 
 468                 0, tagPtr
, &tSearch
); 
 469         while (TkBTreeNextTag(&tSearch
)) { 
 470             TkTextPrintIndex(tSearch
.line1
, tSearch
.ch1
, position
); 
 471             Tcl_AppendElement(interp
, position
, 0); 
 473     } else if ((c 
== 'r') && (strncmp(argv
[2], "remove", length
) == 0) 
 475         fullOption 
= "remove"; 
 479         Tcl_AppendResult(interp
, "bad tag option \"", argv
[2], 
 480                 "\":  must be add, bind, configure, delete, lower, ", 
 481                 "names, nextrange, raise, ranges, or remove", 
 489  *---------------------------------------------------------------------- 
 493  *      Find the record describing a tag within a given text widget, 
 494  *      creating a new record if one doesn't already exist. 
 497  *      The return value is a pointer to the TkTextTag record for tagName. 
 500  *      A new tag record is created if there isn't one already defined 
 503  *---------------------------------------------------------------------- 
 507 TkTextCreateTag(textPtr
, tagName
) 
 508     TkText 
*textPtr
;            /* Widget in which tag is being used. */ 
 509     char *tagName
;              /* Name of desired tag. */ 
 511     register TkTextTag 
*tagPtr
; 
 515     hPtr 
= Tcl_CreateHashEntry(&textPtr
->tagTable
, tagName
, &new); 
 517         return (TkTextTag 
*) Tcl_GetHashValue(hPtr
); 
 521      * No existing entry.  Create a new one, initialize it, and add a 
 522      * pointer to it to the hash table entry. 
 525     tagPtr 
= (TkTextTag 
*) ckalloc(sizeof(TkTextTag
)); 
 526     tagPtr
->name 
= Tcl_GetHashKey(&textPtr
->tagTable
, hPtr
); 
 527     tagPtr
->priority 
= textPtr
->numTags
; 
 528     tagPtr
->border 
= NULL
; 
 529     tagPtr
->borderWidth 
= 1; 
 530     tagPtr
->relief 
= TK_RELIEF_FLAT
; 
 531     tagPtr
->bgStipple 
= None
; 
 532     tagPtr
->fgColor 
= NULL
; 
 533     tagPtr
->fontPtr 
= NULL
; 
 534     tagPtr
->fgStipple 
= None
; 
 535     tagPtr
->underline 
= 0; 
 537     Tcl_SetHashValue(hPtr
, tagPtr
); 
 542  *---------------------------------------------------------------------- 
 546  *      See if tag is defined for a given widget. 
 549  *      If tagName is defined in textPtr, a pointer to its TkTextTag 
 550  *      structure is returned.  Otherwise NULL is returned and an 
 551  *      error message is recorded in interp->result unless interp 
 557  *---------------------------------------------------------------------- 
 561 FindTag(interp
, textPtr
, tagName
) 
 562     Tcl_Interp 
*interp
;         /* Interpreter to use for error message; 
 563                                  * if NULL, then don't record an error 
 565     TkText 
*textPtr
;            /* Widget in which tag is being used. */ 
 566     char *tagName
;              /* Name of desired tag. */ 
 570     hPtr 
= Tcl_FindHashEntry(&textPtr
->tagTable
, tagName
); 
 572         return (TkTextTag 
*) Tcl_GetHashValue(hPtr
); 
 574     if (interp 
!= NULL
) { 
 575         Tcl_AppendResult(interp
, "tag \"", tagName
, 
 576                 "\" isn't defined in text widget", (char *) NULL
); 
 582  *---------------------------------------------------------------------- 
 586  *      This procedure is called when a tag is deleted to free up the 
 587  *      memory and other resources associated with the tag. 
 593  *      Memory and other resources are freed. 
 595  *---------------------------------------------------------------------- 
 599 TkTextFreeTag(tagPtr
) 
 600     register TkTextTag 
*tagPtr
;         /* Tag being deleted. */ 
 602     if (tagPtr
->border 
!= None
) { 
 603         Tk_Free3DBorder(tagPtr
->border
); 
 605     if (tagPtr
->bgStipple 
!= None
) { 
 606         Tk_FreeBitmap(tagPtr
->bgStipple
); 
 608     if (tagPtr
->fgColor 
!= None
) { 
 609         Tk_FreeColor(tagPtr
->fgColor
); 
 611     if (tagPtr
->fgStipple 
!= None
) { 
 612         Tk_FreeBitmap(tagPtr
->fgStipple
); 
 614     ckfree((char *) tagPtr
); 
 618  *---------------------------------------------------------------------- 
 622  *      This procedure sorts an array of tag pointers in increasing 
 623  *      order of priority, optimizing for the common case where the 
 632  *---------------------------------------------------------------------- 
 636 SortTags(numTags
, tagArrayPtr
) 
 637     int numTags
;                /* Number of tag pointers at *tagArrayPtr. */ 
 638     TkTextTag 
**tagArrayPtr
;    /* Pointer to array of pointers. */ 
 641     register TkTextTag 
**tagPtrPtr
; 
 642     TkTextTag 
**maxPtrPtr
, *tmp
; 
 648         for (i 
= numTags
-1; i 
> 0; i
--, tagArrayPtr
++) { 
 649             maxPtrPtr 
= tagPtrPtr 
= tagArrayPtr
; 
 650             prio 
= tagPtrPtr
[0]->priority
; 
 651             for (j 
= i
, tagPtrPtr
++; j 
> 0; j
--, tagPtrPtr
++) { 
 652                 if (tagPtrPtr
[0]->priority 
< prio
) { 
 653                     prio 
= tagPtrPtr
[0]->priority
; 
 654                     maxPtrPtr 
= tagPtrPtr
; 
 658             *maxPtrPtr 
= *tagArrayPtr
; 
 662         qsort((VOID 
*) tagArrayPtr
, numTags
, sizeof (TkTextTag 
*), 
 668  *---------------------------------------------------------------------- 
 672  *      This procedure is called by qsort when sorting an array of 
 673  *      tags in priority order. 
 676  *      The return value is -1 if the first argument should be before 
 677  *      the second element (i.e. it has lower priority), 0 if it's 
 678  *      equivalent (this should never happen!), and 1 if it should be 
 679  *      after the second element. 
 684  *---------------------------------------------------------------------- 
 688 TagSortProc(first
, second
) 
 689     CONST VOID 
*first
, *second
;         /* Elements to be compared. */ 
 691     TkTextTag 
*tagPtr1
, *tagPtr2
; 
 693     tagPtr1 
= * (TkTextTag 
**) first
; 
 694     tagPtr2 
= * (TkTextTag 
**) second
; 
 695     return tagPtr1
->priority 
- tagPtr2
->priority
; 
 699  *---------------------------------------------------------------------- 
 701  * ChangeTagPriority -- 
 703  *      This procedure changes the priority of a tag by modifying 
 704  *      its priority and all other ones whose priority is affected 
 711  *      Priorities may be changed for some or all of the tags in 
 712  *      textPtr.  The tags will be arranged so that there is exactly 
 713  *      one tag at each priority level between 0 and textPtr->numTags-1, 
 714  *      with tagPtr at priority "prio". 
 716  *---------------------------------------------------------------------- 
 720 ChangeTagPriority(textPtr
, tagPtr
, prio
) 
 721     TkText 
*textPtr
;                    /* Information about text widget. */ 
 722     TkTextTag 
*tagPtr
;                  /* Tag whose priority is to be 
 724     int prio
;                           /* New priority for tag. */ 
 726     int low
, high
, delta
; 
 727     register TkTextTag 
*tagPtr2
; 
 729     Tcl_HashSearch search
; 
 734     if (prio 
>= textPtr
->numTags
) { 
 735         prio 
= textPtr
->numTags
-1; 
 737     if (prio 
== tagPtr
->priority
) { 
 739     } else if (prio 
< tagPtr
->priority
) { 
 741         high 
= tagPtr
->priority
-1; 
 744         low 
= tagPtr
->priority
+1; 
 748     for (hPtr 
= Tcl_FirstHashEntry(&textPtr
->tagTable
, &search
); 
 749             hPtr 
!= NULL
; hPtr 
= Tcl_NextHashEntry(&search
)) { 
 750         tagPtr2 
= (TkTextTag 
*) Tcl_GetHashValue(hPtr
); 
 751         if ((tagPtr2
->priority 
>= low
) && (tagPtr2
->priority 
<= high
)) { 
 752             tagPtr2
->priority 
+= delta
; 
 755     tagPtr
->priority 
= prio
; 
 759  *-------------------------------------------------------------- 
 763  *      This procedure is invoked by the Tk dispatcher to handle 
 764  *      events associated with bindings on items. 
 770  *      Depends on the command invoked as part of the binding 
 771  *      (if there was any). 
 773  *-------------------------------------------------------------- 
 777 TkTextBindProc(clientData
, eventPtr
) 
 778     ClientData clientData
;              /* Pointer to canvas structure. */ 
 779     XEvent 
*eventPtr
;                   /* Pointer to X event that just 
 782     TkText 
*textPtr 
= (TkText 
*) clientData
; 
 785     Tk_Preserve((ClientData
) textPtr
); 
 788      * This code simulates grabs for mouse buttons by refusing to 
 789      * pick a new current character between the time a mouse button goes 
 790      * down and the time when the last mouse button is released. 
 793     if (eventPtr
->type 
== ButtonPress
) { 
 794         textPtr
->flags 
|= BUTTON_DOWN
; 
 795     } else if (eventPtr
->type 
== ButtonRelease
) { 
 798         switch (eventPtr
->xbutton
.button
) { 
 818         if ((eventPtr
->xbutton
.state 
& (Button1Mask
|Button2Mask
 
 819                 |Button3Mask
|Button4Mask
|Button5Mask
)) == mask
) { 
 820             textPtr
->flags 
&= ~BUTTON_DOWN
; 
 823     } else if ((eventPtr
->type 
== EnterNotify
) 
 824             || (eventPtr
->type 
== LeaveNotify
)) { 
 825         TkTextPickCurrent(textPtr
, eventPtr
); 
 827     } else if (eventPtr
->type 
== MotionNotify
) { 
 828         TkTextPickCurrent(textPtr
, eventPtr
); 
 830     TextDoEvent(textPtr
, eventPtr
); 
 832         unsigned int oldState
; 
 834         oldState 
= eventPtr
->xbutton
.state
; 
 835         eventPtr
->xbutton
.state 
&= ~(Button1Mask
|Button2Mask
 
 836                 |Button3Mask
|Button4Mask
|Button5Mask
); 
 837         TkTextPickCurrent(textPtr
, eventPtr
); 
 838         eventPtr
->xbutton
.state 
= oldState
; 
 842     Tk_Release((ClientData
) textPtr
); 
 846  *-------------------------------------------------------------- 
 848  * TkTextPickCurrent -- 
 850  *      Find the topmost item in a canvas that contains a given 
 851  *      location and mark the the current item.  If the current 
 852  *      item has changed, generate a fake exit event on the old 
 853  *      current item and a fake enter event on the new current 
 860  *      The current item for textPtr may change.  If it does, 
 861  *      then the commands associated with item entry and exit 
 862  *      could do just about anything. 
 864  *-------------------------------------------------------------- 
 868 TkTextPickCurrent(textPtr
, eventPtr
) 
 869     register TkText 
*textPtr
;           /* Text widget in which to select 
 870                                          * current character. */ 
 871     XEvent 
*eventPtr
;                   /* Event describing location of 
 872                                          * mouse cursor.  Must be EnterWindow, 
 873                                          * LeaveWindow, ButtonRelease, or 
 880      * If a button is down, then don't do anything at all;  we'll be 
 881      * called again when all buttons are up, and we can repick then. 
 882      * This implements a form of mouse grabbing. 
 885     if (textPtr
->flags 
& BUTTON_DOWN
) { 
 890      * Save information about this event in the widget for use if we have 
 891      * to synthesize more enter and leave events later (e.g. because a 
 892      * character was deleting, causing a new character to be underneath 
 893      * the mouse cursor).  Also translate MotionNotify events into 
 894      * EnterNotify events, since that's what gets reported to event 
 895      * handlers when the current character changes. 
 898     if (eventPtr 
!= &textPtr
->pickEvent
) { 
 899         if ((eventPtr
->type 
== MotionNotify
) 
 900                 || (eventPtr
->type 
== ButtonRelease
)) { 
 901             textPtr
->pickEvent
.xcrossing
.type 
= EnterNotify
; 
 902             textPtr
->pickEvent
.xcrossing
.serial 
= eventPtr
->xmotion
.serial
; 
 903             textPtr
->pickEvent
.xcrossing
.send_event
 
 904                     = eventPtr
->xmotion
.send_event
; 
 905             textPtr
->pickEvent
.xcrossing
.display 
= eventPtr
->xmotion
.display
; 
 906             textPtr
->pickEvent
.xcrossing
.window 
= eventPtr
->xmotion
.window
; 
 907             textPtr
->pickEvent
.xcrossing
.root 
= eventPtr
->xmotion
.root
; 
 908             textPtr
->pickEvent
.xcrossing
.subwindow 
= None
; 
 909             textPtr
->pickEvent
.xcrossing
.time 
= eventPtr
->xmotion
.time
; 
 910             textPtr
->pickEvent
.xcrossing
.x 
= eventPtr
->xmotion
.x
; 
 911             textPtr
->pickEvent
.xcrossing
.y 
= eventPtr
->xmotion
.y
; 
 912             textPtr
->pickEvent
.xcrossing
.x_root 
= eventPtr
->xmotion
.x_root
; 
 913             textPtr
->pickEvent
.xcrossing
.y_root 
= eventPtr
->xmotion
.y_root
; 
 914             textPtr
->pickEvent
.xcrossing
.mode 
= NotifyNormal
; 
 915             textPtr
->pickEvent
.xcrossing
.detail 
= NotifyNonlinear
; 
 916             textPtr
->pickEvent
.xcrossing
.same_screen
 
 917                     = eventPtr
->xmotion
.same_screen
; 
 918             textPtr
->pickEvent
.xcrossing
.focus 
= False
; 
 919             textPtr
->pickEvent
.xcrossing
.state 
= eventPtr
->xmotion
.state
; 
 921             textPtr
->pickEvent 
= *eventPtr
; 
 926     if (textPtr
->pickEvent
.type 
!= LeaveNotify
) { 
 927         linePtr 
= TkTextCharAtLoc(textPtr
, textPtr
->pickEvent
.xcrossing
.x
, 
 928                 textPtr
->pickEvent
.xcrossing
.y
, &ch
); 
 932      * Simulate a LeaveNotify event on the previous current character and 
 933      * an EnterNotify event on the new current character.  Also, move the 
 934      * "current" mark to its new place. 
 937     if (textPtr
->flags 
& IN_CURRENT
) { 
 938         if ((linePtr 
== textPtr
->currentAnnotPtr
->linePtr
) 
 939                 && (ch 
== textPtr
->currentAnnotPtr
->ch
)) { 
 943         if (linePtr 
== NULL
) { 
 947     if (textPtr
->flags 
& IN_CURRENT
) { 
 950         event 
= textPtr
->pickEvent
; 
 951         event
.type 
= LeaveNotify
; 
 952         TextDoEvent(textPtr
, &event
); 
 953         textPtr
->flags 
&= ~IN_CURRENT
; 
 955     if (linePtr 
!= NULL
) { 
 958         TkBTreeRemoveAnnotation(textPtr
->currentAnnotPtr
); 
 959         textPtr
->currentAnnotPtr
->linePtr 
= linePtr
; 
 960         textPtr
->currentAnnotPtr
->ch 
= ch
; 
 961         TkBTreeAddAnnotation(textPtr
->currentAnnotPtr
); 
 962         event 
= textPtr
->pickEvent
; 
 963         event
.type 
= EnterNotify
; 
 964         TextDoEvent(textPtr
, &event
); 
 965         textPtr
->flags 
|= IN_CURRENT
; 
 970  *---------------------------------------------------------------------- 
 972  * TkTextUnpickCurrent -- 
 974  *      This procedure is called when the "current" character is 
 975  *      deleted:  it synthesizes a "leave" event for the character. 
 981  *      A binding associated with one of the tags on the current 
 982  *      character may be triggered. 
 984  *---------------------------------------------------------------------- 
 988 TkTextUnpickCurrent(textPtr
) 
 989     TkText 
*textPtr
;            /* Text widget information. */ 
 991     if (textPtr
->flags 
& IN_CURRENT
) { 
 994         event 
= textPtr
->pickEvent
; 
 995         event
.type 
= LeaveNotify
; 
 996         TextDoEvent(textPtr
, &event
); 
 997         textPtr
->flags 
&= ~IN_CURRENT
; 
1002  *-------------------------------------------------------------- 
1006  *      This procedure is called to invoke binding processing 
1007  *      for a new event that is associated with the current character 
1008  *      for a text widget. 
1014  *      Depends on the bindings for the text. 
1016  *-------------------------------------------------------------- 
1020 TextDoEvent(textPtr
, eventPtr
) 
1021     TkText 
*textPtr
;                    /* Text widget in which event 
1023     XEvent 
*eventPtr
;                   /* Real or simulated X event that 
1024                                          * is to be processed. */ 
1026     TkTextTag 
**tagArrayPtr
, **p1
, **p2
, *tmp
; 
1029     if (textPtr
->bindingTable 
== NULL
) { 
1034      * Set up an array containing all of the tags that are associated 
1035      * with the current character.  This array will be used to look 
1036      * for bindings.  If there are no tags then there can't be any 
1040     tagArrayPtr 
= TkBTreeGetTags(textPtr
->tree
, 
1041             textPtr
->currentAnnotPtr
->linePtr
, textPtr
->currentAnnotPtr
->ch
, 
1048      * Sort the array of tags.  SortTags sorts it backwards, so after it 
1049      * returns we have to reverse the order in the array. 
1052     SortTags(numTags
, tagArrayPtr
); 
1053     for (p1 
= tagArrayPtr
, p2 
= tagArrayPtr 
+ numTags 
- 1; 
1054             p1 
< p2
;  p1
++, p2
--) { 
1061      * Invoke the binding system, then free up the tag array. 
1064     Tk_BindEvent(textPtr
->bindingTable
, eventPtr
, textPtr
->tkwin
, 
1065             numTags
, (ClientData 
*) tagArrayPtr
); 
1066     ckfree((char *) tagArrayPtr
);