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
);