1/*
2 * tkTextTag.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-1997 Sun Microsystems, Inc.
10 *
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 *
14 * RCS: @(#) $Id: tkTextTag.c,v 1.8.2.1 2006/10/17 05:38:48 dgp Exp $
15 */
16
17#include "default.h"
18#include "tkPort.h"
19#include "tkInt.h"
20#include "tkText.h"
21
22static Tk_ConfigSpec tagConfigSpecs[] = {
23    {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
24	(char *) NULL, Tk_Offset(TkTextTag, border), TK_CONFIG_NULL_OK},
25    {TK_CONFIG_BITMAP, "-bgstipple", (char *) NULL, (char *) NULL,
26	(char *) NULL, Tk_Offset(TkTextTag, bgStipple), TK_CONFIG_NULL_OK},
27    {TK_CONFIG_STRING, "-borderwidth", (char *) NULL, (char *) NULL,
28	"0", Tk_Offset(TkTextTag, bdString),
29	TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK},
30    {TK_CONFIG_STRING, "-elide", (char *) NULL, (char *) NULL,
31	"0", Tk_Offset(TkTextTag, elideString),
32	TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK},
33    {TK_CONFIG_BITMAP, "-fgstipple", (char *) NULL, (char *) NULL,
34	(char *) NULL, Tk_Offset(TkTextTag, fgStipple), TK_CONFIG_NULL_OK},
35    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
36	(char *) NULL, Tk_Offset(TkTextTag, tkfont), TK_CONFIG_NULL_OK},
37    {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
38	(char *) NULL, Tk_Offset(TkTextTag, fgColor), TK_CONFIG_NULL_OK},
39    {TK_CONFIG_STRING, "-justify", (char *) NULL, (char *) NULL,
40	(char *) NULL, Tk_Offset(TkTextTag, justifyString), TK_CONFIG_NULL_OK},
41    {TK_CONFIG_STRING, "-lmargin1", (char *) NULL, (char *) NULL,
42	(char *) NULL, Tk_Offset(TkTextTag, lMargin1String), TK_CONFIG_NULL_OK},
43    {TK_CONFIG_STRING, "-lmargin2", (char *) NULL, (char *) NULL,
44	(char *) NULL, Tk_Offset(TkTextTag, lMargin2String), TK_CONFIG_NULL_OK},
45    {TK_CONFIG_STRING, "-offset", (char *) NULL, (char *) NULL,
46	(char *) NULL, Tk_Offset(TkTextTag, offsetString), TK_CONFIG_NULL_OK},
47    {TK_CONFIG_STRING, "-overstrike", (char *) NULL, (char *) NULL,
48	(char *) NULL, Tk_Offset(TkTextTag, overstrikeString),
49	TK_CONFIG_NULL_OK},
50    {TK_CONFIG_STRING, "-relief", (char *) NULL, (char *) NULL,
51	(char *) NULL, Tk_Offset(TkTextTag, reliefString), TK_CONFIG_NULL_OK},
52    {TK_CONFIG_STRING, "-rmargin", (char *) NULL, (char *) NULL,
53	(char *) NULL, Tk_Offset(TkTextTag, rMarginString), TK_CONFIG_NULL_OK},
54    {TK_CONFIG_STRING, "-spacing1", (char *) NULL, (char *) NULL,
55	(char *) NULL, Tk_Offset(TkTextTag, spacing1String), TK_CONFIG_NULL_OK},
56    {TK_CONFIG_STRING, "-spacing2", (char *) NULL, (char *) NULL,
57	(char *) NULL, Tk_Offset(TkTextTag, spacing2String), TK_CONFIG_NULL_OK},
58    {TK_CONFIG_STRING, "-spacing3", (char *) NULL, (char *) NULL,
59	(char *) NULL, Tk_Offset(TkTextTag, spacing3String), TK_CONFIG_NULL_OK},
60    {TK_CONFIG_STRING, "-tabs", (char *) NULL, (char *) NULL,
61	(char *) NULL, Tk_Offset(TkTextTag, tabString), TK_CONFIG_NULL_OK},
62    {TK_CONFIG_STRING, "-underline", (char *) NULL, (char *) NULL,
63	(char *) NULL, Tk_Offset(TkTextTag, underlineString),
64	TK_CONFIG_NULL_OK},
65    {TK_CONFIG_CUSTOM, "-wrap", (char *) NULL, (char *) NULL,
66	(char *) NULL, Tk_Offset(TkTextTag, wrapMode),
67	TK_CONFIG_NULL_OK, &TkTextWrapModeOption},
68    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
69	(char *) NULL, 0, 0}
70};
71
72/*
73 * Forward declarations for procedures defined later in this file:
74 */
75
76static void		ChangeTagPriority _ANSI_ARGS_((TkText *textPtr,
77			    TkTextTag *tagPtr, int prio));
78static TkTextTag *	FindTag _ANSI_ARGS_((Tcl_Interp *interp,
79			    TkText *textPtr, CONST char *tagName));
80static void		SortTags _ANSI_ARGS_((int numTags,
81			    TkTextTag **tagArrayPtr));
82static int		TagSortProc _ANSI_ARGS_((CONST VOID *first,
83			    CONST VOID *second));
84
85/*
86 *--------------------------------------------------------------
87 *
88 * TkTextTagCmd --
89 *
90 *	This procedure is invoked to process the "tag" options of
91 *	the widget command for text widgets. See the user documentation
92 *	for details on what it does.
93 *
94 * Results:
95 *	A standard Tcl result.
96 *
97 * Side effects:
98 *	See the user documentation.
99 *
100 *--------------------------------------------------------------
101 */
102
103int
104TkTextTagCmd(textPtr, interp, argc, argv)
105    register TkText *textPtr;	/* Information about text widget. */
106    Tcl_Interp *interp;		/* Current interpreter. */
107    int argc;			/* Number of arguments. */
108    CONST char **argv;		/* Argument strings.  Someone else has already
109				 * parsed this command enough to know that
110				 * argv[1] is "tag". */
111{
112    int c, i, addTag;
113    size_t length;
114    char *fullOption;
115    register TkTextTag *tagPtr;
116    TkTextIndex first, last, index1, index2;
117
118    if (argc < 3) {
119	Tcl_AppendResult(interp, "wrong # args: should be \"",
120		argv[0], " tag option ?arg arg ...?\"", (char *) NULL);
121	return TCL_ERROR;
122    }
123    c = argv[2][0];
124    length = strlen(argv[2]);
125    if ((c == 'a') && (strncmp(argv[2], "add", length) == 0)) {
126	fullOption = "add";
127	addTag = 1;
128
129	addAndRemove:
130	if (argc < 5) {
131	    Tcl_AppendResult(interp, "wrong # args: should be \"",
132		    argv[0], " tag ", fullOption,
133		    " tagName index1 ?index2 index1 index2 ...?\"",
134		    (char *) NULL);
135	    return TCL_ERROR;
136	}
137	tagPtr = TkTextCreateTag(textPtr, argv[3]);
138	for (i = 4; i < argc; i += 2) {
139	    if (TkTextGetIndex(interp, textPtr, argv[i], &index1) != TCL_OK) {
140		return TCL_ERROR;
141	    }
142	    if (argc > (i+1)) {
143		if (TkTextGetIndex(interp, textPtr, argv[i+1], &index2)
144			!= TCL_OK) {
145		    return TCL_ERROR;
146		}
147		if (TkTextIndexCmp(&index1, &index2) >= 0) {
148		    return TCL_OK;
149		}
150	    } else {
151		index2 = index1;
152		TkTextIndexForwChars(&index2, 1, &index2);
153	    }
154
155	    if (tagPtr->affectsDisplay) {
156		TkTextRedrawTag(textPtr, &index1, &index2, tagPtr, !addTag);
157	    } else {
158		/*
159		 * Still need to trigger enter/leave events on tags that
160		 * have changed.
161		 */
162
163		TkTextEventuallyRepick(textPtr);
164	    }
165	    TkBTreeTag(&index1, &index2, tagPtr, addTag);
166
167	    /*
168	     * If the tag is "sel" then grab the selection if we're supposed
169	     * to export it and don't already have it.  Also, invalidate
170	     * partially-completed selection retrievals.
171	     */
172
173	    if (tagPtr == textPtr->selTagPtr) {
174		XEvent event;
175		/*
176		 * Send an event that the selection changed.
177		 * This is equivalent to
178		 * "event generate $textWidget <<Selection>>"
179		 */
180
181		memset((VOID *) &event, 0, sizeof(event));
182		event.xany.type = VirtualEvent;
183		event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
184		event.xany.send_event = False;
185		event.xany.window = Tk_WindowId(textPtr->tkwin);
186		event.xany.display = Tk_Display(textPtr->tkwin);
187		((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
188		Tk_HandleEvent(&event);
189
190		if (addTag && textPtr->exportSelection
191			&& !(textPtr->flags & GOT_SELECTION)) {
192		    Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY,
193			    TkTextLostSelection, (ClientData) textPtr);
194		    textPtr->flags |= GOT_SELECTION;
195		}
196		textPtr->abortSelections = 1;
197	    }
198	}
199    } else if ((c == 'b') && (strncmp(argv[2], "bind", length) == 0)) {
200	if ((argc < 4) || (argc > 6)) {
201	    Tcl_AppendResult(interp, "wrong # args: should be \"",
202		    argv[0], " tag bind tagName ?sequence? ?command?\"",
203		    (char *) NULL);
204	    return TCL_ERROR;
205	}
206	tagPtr = TkTextCreateTag(textPtr, argv[3]);
207
208	/*
209	 * Make a binding table if the widget doesn't already have
210	 * one.
211	 */
212
213	if (textPtr->bindingTable == NULL) {
214	    textPtr->bindingTable = Tk_CreateBindingTable(interp);
215	}
216
217	if (argc == 6) {
218	    int append = 0;
219	    unsigned long mask;
220
221	    if (argv[5][0] == 0) {
222		return Tk_DeleteBinding(interp, textPtr->bindingTable,
223			(ClientData) tagPtr, argv[4]);
224	    }
225	    if (argv[5][0] == '+') {
226		argv[5]++;
227		append = 1;
228	    }
229	    mask = Tk_CreateBinding(interp, textPtr->bindingTable,
230		    (ClientData) tagPtr, argv[4], argv[5], append);
231	    if (mask == 0) {
232		return TCL_ERROR;
233	    }
234	    if (mask & (unsigned) ~(ButtonMotionMask|Button1MotionMask
235		    |Button2MotionMask|Button3MotionMask|Button4MotionMask
236		    |Button5MotionMask|ButtonPressMask|ButtonReleaseMask
237		    |EnterWindowMask|LeaveWindowMask|KeyPressMask
238		    |KeyReleaseMask|PointerMotionMask|VirtualEventMask)) {
239		Tk_DeleteBinding(interp, textPtr->bindingTable,
240			(ClientData) tagPtr, argv[4]);
241		Tcl_ResetResult(interp);
242		Tcl_AppendResult(interp, "requested illegal events; ",
243			"only key, button, motion, enter, leave, and virtual ",
244			"events may be used", (char *) NULL);
245		return TCL_ERROR;
246	    }
247	} else if (argc == 5) {
248	    CONST char *command;
249
250	    command = Tk_GetBinding(interp, textPtr->bindingTable,
251		    (ClientData) tagPtr, argv[4]);
252	    if (command == NULL) {
253		CONST char *string = Tcl_GetStringResult(interp);
254
255		/*
256		 * Ignore missing binding errors.  This is a special hack
257		 * that relies on the error message returned by FindSequence
258		 * in tkBind.c.
259		 */
260
261		if (string[0] != '\0') {
262		    return TCL_ERROR;
263		} else {
264		    Tcl_ResetResult(interp);
265		}
266	    } else {
267		Tcl_SetResult(interp, (char *) command, TCL_STATIC);
268	    }
269	} else {
270	    Tk_GetAllBindings(interp, textPtr->bindingTable,
271		    (ClientData) tagPtr);
272	}
273    } else if ((c == 'c') && (strncmp(argv[2], "cget", length) == 0)
274	    && (length >= 2)) {
275	if (argc != 5) {
276	    Tcl_AppendResult(interp, "wrong # args: should be \"",
277		    argv[0], " tag cget tagName option\"",
278		    (char *) NULL);
279	    return TCL_ERROR;
280	}
281	tagPtr = FindTag(interp, textPtr, argv[3]);
282	if (tagPtr == NULL) {
283	    return TCL_ERROR;
284	}
285	return Tk_ConfigureValue(interp, textPtr->tkwin, tagConfigSpecs,
286		(char *) tagPtr, argv[4], 0);
287    } else if ((c == 'c') && (strncmp(argv[2], "configure", length) == 0)
288	    && (length >= 2)) {
289	if (argc < 4) {
290	    Tcl_AppendResult(interp, "wrong # args: should be \"",
291		    argv[0], " tag configure tagName ?option? ?value? ",
292		    "?option value ...?\"", (char *) NULL);
293	    return TCL_ERROR;
294	}
295	tagPtr = TkTextCreateTag(textPtr, argv[3]);
296	if (argc == 4) {
297	    return Tk_ConfigureInfo(interp, textPtr->tkwin, tagConfigSpecs,
298		    (char *) tagPtr, (char *) NULL, 0);
299	} else if (argc == 5) {
300	    return Tk_ConfigureInfo(interp, textPtr->tkwin, tagConfigSpecs,
301		    (char *) tagPtr, argv[4], 0);
302	} else {
303	    int result;
304
305	    result = Tk_ConfigureWidget(interp, textPtr->tkwin, tagConfigSpecs,
306		    argc-4, argv+4, (char *) tagPtr, 0);
307	    /*
308	     * Some of the configuration options, like -underline
309	     * and -justify, require additional translation (this is
310	     * needed because we need to distinguish a particular value
311	     * of an option from "unspecified").
312	     */
313
314	    if (tagPtr->bdString != NULL) {
315		if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->bdString,
316			&tagPtr->borderWidth) != TCL_OK) {
317		    return TCL_ERROR;
318		}
319		if (tagPtr->borderWidth < 0) {
320		    tagPtr->borderWidth = 0;
321		}
322	    }
323	    if (tagPtr->reliefString != NULL) {
324		if (Tk_GetRelief(interp, tagPtr->reliefString,
325			&tagPtr->relief) != TCL_OK) {
326		    return TCL_ERROR;
327		}
328	    }
329	    if (tagPtr->justifyString != NULL) {
330		if (Tk_GetJustify(interp, tagPtr->justifyString,
331			&tagPtr->justify) != TCL_OK) {
332		    return TCL_ERROR;
333		}
334	    }
335	    if (tagPtr->lMargin1String != NULL) {
336		if (Tk_GetPixels(interp, textPtr->tkwin,
337			tagPtr->lMargin1String, &tagPtr->lMargin1) != TCL_OK) {
338		    return TCL_ERROR;
339		}
340	    }
341	    if (tagPtr->lMargin2String != NULL) {
342		if (Tk_GetPixels(interp, textPtr->tkwin,
343			tagPtr->lMargin2String, &tagPtr->lMargin2) != TCL_OK) {
344		    return TCL_ERROR;
345		}
346	    }
347	    if (tagPtr->offsetString != NULL) {
348		if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->offsetString,
349			&tagPtr->offset) != TCL_OK) {
350		    return TCL_ERROR;
351		}
352	    }
353	    if (tagPtr->overstrikeString != NULL) {
354		if (Tcl_GetBoolean(interp, tagPtr->overstrikeString,
355			&tagPtr->overstrike) != TCL_OK) {
356		    return TCL_ERROR;
357		}
358	    }
359	    if (tagPtr->rMarginString != NULL) {
360		if (Tk_GetPixels(interp, textPtr->tkwin,
361			tagPtr->rMarginString, &tagPtr->rMargin) != TCL_OK) {
362		    return TCL_ERROR;
363		}
364	    }
365	    if (tagPtr->spacing1String != NULL) {
366		if (Tk_GetPixels(interp, textPtr->tkwin,
367			tagPtr->spacing1String, &tagPtr->spacing1) != TCL_OK) {
368		    return TCL_ERROR;
369		}
370		if (tagPtr->spacing1 < 0) {
371		    tagPtr->spacing1 = 0;
372		}
373	    }
374	    if (tagPtr->spacing2String != NULL) {
375		if (Tk_GetPixels(interp, textPtr->tkwin,
376			tagPtr->spacing2String, &tagPtr->spacing2) != TCL_OK) {
377		    return TCL_ERROR;
378		}
379		if (tagPtr->spacing2 < 0) {
380		    tagPtr->spacing2 = 0;
381		}
382	    }
383	    if (tagPtr->spacing3String != NULL) {
384		if (Tk_GetPixels(interp, textPtr->tkwin,
385			tagPtr->spacing3String, &tagPtr->spacing3) != TCL_OK) {
386		    return TCL_ERROR;
387		}
388		if (tagPtr->spacing3 < 0) {
389		    tagPtr->spacing3 = 0;
390		}
391	    }
392	    if (tagPtr->tabArrayPtr != NULL) {
393		ckfree((char *) tagPtr->tabArrayPtr);
394		tagPtr->tabArrayPtr = NULL;
395	    }
396	    if (tagPtr->tabString != NULL) {
397		tagPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr->tkwin,
398			tagPtr->tabString);
399		if (tagPtr->tabArrayPtr == NULL) {
400		    return TCL_ERROR;
401		}
402	    }
403	    if (tagPtr->underlineString != NULL) {
404		if (Tcl_GetBoolean(interp, tagPtr->underlineString,
405			&tagPtr->underline) != TCL_OK) {
406		    return TCL_ERROR;
407		}
408	    }
409	    if (tagPtr->elideString != NULL) {
410		if (Tcl_GetBoolean(interp, tagPtr->elideString,
411			&tagPtr->elide) != TCL_OK) {
412		    return TCL_ERROR;
413		}
414	    }
415
416	    /*
417	     * If the "sel" tag was changed, be sure to mirror information
418	     * from the tag back into the text widget record.   NOTE: we
419	     * don't have to free up information in the widget record
420	     * before overwriting it, because it was mirrored in the tag
421	     * and hence freed when the tag field was overwritten.
422	     */
423
424	    if (tagPtr == textPtr->selTagPtr) {
425		textPtr->selBorder = tagPtr->border;
426		textPtr->selBdString = tagPtr->bdString;
427		textPtr->selFgColorPtr = tagPtr->fgColor;
428	    }
429	    tagPtr->affectsDisplay = 0;
430	    if ((tagPtr->border != NULL)
431		    || (tagPtr->bdString != NULL)
432		    || (tagPtr->reliefString != NULL)
433		    || (tagPtr->bgStipple != None)
434		    || (tagPtr->fgColor != NULL) || (tagPtr->tkfont != None)
435		    || (tagPtr->fgStipple != None)
436		    || (tagPtr->justifyString != NULL)
437		    || (tagPtr->lMargin1String != NULL)
438		    || (tagPtr->lMargin2String != NULL)
439		    || (tagPtr->offsetString != NULL)
440		    || (tagPtr->overstrikeString != NULL)
441		    || (tagPtr->rMarginString != NULL)
442		    || (tagPtr->spacing1String != NULL)
443		    || (tagPtr->spacing2String != NULL)
444		    || (tagPtr->spacing3String != NULL)
445		    || (tagPtr->tabString != NULL)
446		    || (tagPtr->underlineString != NULL)
447		    || (tagPtr->elideString != NULL)
448		    || (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
449		tagPtr->affectsDisplay = 1;
450	    }
451	    TkTextRedrawTag(textPtr, (TkTextIndex *) NULL,
452		    (TkTextIndex *) NULL, tagPtr, 1);
453	    return result;
454	}
455    } else if ((c == 'd') && (strncmp(argv[2], "delete", length) == 0)) {
456	Tcl_HashEntry *hPtr;
457
458	if (argc < 4) {
459	    Tcl_AppendResult(interp, "wrong # args: should be \"",
460		    argv[0], " tag delete tagName tagName ...\"",
461		    (char *) NULL);
462	    return TCL_ERROR;
463	}
464	for (i = 3; i < argc; i++) {
465	    hPtr = Tcl_FindHashEntry(&textPtr->tagTable, argv[i]);
466	    if (hPtr == NULL) {
467		continue;
468	    }
469	    tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
470	    if (tagPtr == textPtr->selTagPtr) {
471		continue;
472	    }
473	    if (tagPtr->affectsDisplay) {
474		TkTextRedrawTag(textPtr, (TkTextIndex *) NULL,
475			(TkTextIndex *) NULL, tagPtr, 1);
476	    }
477	    TkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
478	    TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
479		    0, &last),
480	    TkBTreeTag(&first, &last, tagPtr, 0);
481
482	    if (tagPtr == textPtr->selTagPtr) {
483		XEvent event;
484		/*
485		 * Send an event that the selection changed.
486		 * This is equivalent to
487		 * "event generate $textWidget <<Selection>>"
488		 */
489
490		memset((VOID *) &event, 0, sizeof(event));
491		event.xany.type = VirtualEvent;
492		event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
493		event.xany.send_event = False;
494		event.xany.window = Tk_WindowId(textPtr->tkwin);
495		event.xany.display = Tk_Display(textPtr->tkwin);
496		((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
497		Tk_HandleEvent(&event);
498	    }
499
500	    Tcl_DeleteHashEntry(hPtr);
501	    if (textPtr->bindingTable != NULL) {
502		Tk_DeleteAllBindings(textPtr->bindingTable,
503			(ClientData) tagPtr);
504	    }
505
506	    /*
507	     * Update the tag priorities to reflect the deletion of this tag.
508	     */
509
510	    ChangeTagPriority(textPtr, tagPtr, textPtr->numTags-1);
511	    textPtr->numTags -= 1;
512	    TkTextFreeTag(textPtr, tagPtr);
513	}
514    } else if ((c == 'l') && (strncmp(argv[2], "lower", length) == 0)) {
515	TkTextTag *tagPtr2;
516	int prio;
517
518	if ((argc != 4) && (argc != 5)) {
519	    Tcl_AppendResult(interp, "wrong # args: should be \"",
520		    argv[0], " tag lower tagName ?belowThis?\"",
521		    (char *) NULL);
522	    return TCL_ERROR;
523	}
524	tagPtr = FindTag(interp, textPtr, argv[3]);
525	if (tagPtr == NULL) {
526	    return TCL_ERROR;
527	}
528	if (argc == 5) {
529	    tagPtr2 = FindTag(interp, textPtr, argv[4]);
530	    if (tagPtr2 == NULL) {
531		return TCL_ERROR;
532	    }
533	    if (tagPtr->priority < tagPtr2->priority) {
534		prio = tagPtr2->priority - 1;
535	    } else {
536		prio = tagPtr2->priority;
537	    }
538	} else {
539	    prio = 0;
540	}
541	ChangeTagPriority(textPtr, tagPtr, prio);
542	TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
543		tagPtr, 1);
544    } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)
545	    && (length >= 2)) {
546	TkTextTag **arrayPtr;
547	int arraySize;
548
549	if ((argc != 3) && (argc != 4)) {
550	    Tcl_AppendResult(interp, "wrong # args: should be \"",
551		    argv[0], " tag names ?index?\"",
552		    (char *) NULL);
553	    return TCL_ERROR;
554	}
555	if (argc == 3) {
556	    Tcl_HashSearch search;
557	    Tcl_HashEntry *hPtr;
558
559	    arrayPtr = (TkTextTag **) ckalloc((unsigned)
560		    (textPtr->numTags * sizeof(TkTextTag *)));
561	    for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
562		    hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
563		arrayPtr[i] = (TkTextTag *) Tcl_GetHashValue(hPtr);
564	    }
565	    arraySize = textPtr->numTags;
566	} else {
567	    if (TkTextGetIndex(interp, textPtr, argv[3], &index1)
568		    != TCL_OK) {
569		return TCL_ERROR;
570	    }
571	    arrayPtr = TkBTreeGetTags(&index1, &arraySize);
572	    if (arrayPtr == NULL) {
573		return TCL_OK;
574	    }
575	}
576	SortTags(arraySize, arrayPtr);
577	for (i = 0; i < arraySize; i++) {
578	    tagPtr = arrayPtr[i];
579	    Tcl_AppendElement(interp, tagPtr->name);
580	}
581	ckfree((char *) arrayPtr);
582    } else if ((c == 'n') && (strncmp(argv[2], "nextrange", length) == 0)
583	    && (length >= 2)) {
584	TkTextSearch tSearch;
585	char position[TK_POS_CHARS];
586
587	if ((argc != 5) && (argc != 6)) {
588	    Tcl_AppendResult(interp, "wrong # args: should be \"",
589		    argv[0], " tag nextrange tagName index1 ?index2?\"",
590		    (char *) NULL);
591	    return TCL_ERROR;
592	}
593	tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]);
594	if (tagPtr == NULL) {
595	    return TCL_OK;
596	}
597	if (TkTextGetIndex(interp, textPtr, argv[4], &index1) != TCL_OK) {
598	    return TCL_ERROR;
599	}
600	TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
601		0, &last);
602	if (argc == 5) {
603	    index2 = last;
604	} else if (TkTextGetIndex(interp, textPtr, argv[5], &index2)
605		!= TCL_OK) {
606	    return TCL_ERROR;
607	}
608
609	/*
610	 * The search below is a bit tricky.  Rather than use the B-tree
611	 * facilities to stop the search at index2, let it search up
612	 * until the end of the file but check for a position past index2
613	 * ourselves.  The reason for doing it this way is that we only
614	 * care whether the *start* of the range is before index2;  once
615	 * we find the start, we don't want TkBTreeNextTag to abort the
616	 * search because the end of the range is after index2.
617	 */
618
619	TkBTreeStartSearch(&index1, &last, tagPtr, &tSearch);
620	if (TkBTreeCharTagged(&index1, tagPtr)) {
621	    TkTextSegment *segPtr;
622	    int offset;
623
624	    /*
625	     * The first character is tagged.  See if there is an
626	     * on-toggle just before the character.  If not, then
627	     * skip to the end of this tagged range.
628	     */
629
630	    for (segPtr = index1.linePtr->segPtr, offset = index1.byteIndex;
631		    offset >= 0;
632		    offset -= segPtr->size, segPtr = segPtr->nextPtr) {
633		if ((offset == 0) && (segPtr->typePtr == &tkTextToggleOnType)
634			&& (segPtr->body.toggle.tagPtr == tagPtr)) {
635		    goto gotStart;
636		}
637	    }
638	    if (!TkBTreeNextTag(&tSearch)) {
639		 return TCL_OK;
640	    }
641	}
642
643	/*
644	 * Find the start of the tagged range.
645	 */
646
647	if (!TkBTreeNextTag(&tSearch)) {
648	    return TCL_OK;
649	}
650	gotStart:
651	if (TkTextIndexCmp(&tSearch.curIndex, &index2) >= 0) {
652	    return TCL_OK;
653	}
654	TkTextPrintIndex(&tSearch.curIndex, position);
655	Tcl_AppendElement(interp, position);
656	TkBTreeNextTag(&tSearch);
657	TkTextPrintIndex(&tSearch.curIndex, position);
658	Tcl_AppendElement(interp, position);
659    } else if ((c == 'p') && (strncmp(argv[2], "prevrange", length) == 0)
660	    && (length >= 2)) {
661	TkTextSearch tSearch;
662	char position1[TK_POS_CHARS];
663	char position2[TK_POS_CHARS];
664
665	if ((argc != 5) && (argc != 6)) {
666	    Tcl_AppendResult(interp, "wrong # args: should be \"",
667		    argv[0], " tag prevrange tagName index1 ?index2?\"",
668		    (char *) NULL);
669	    return TCL_ERROR;
670	}
671	tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]);
672	if (tagPtr == NULL) {
673	    return TCL_OK;
674	}
675	if (TkTextGetIndex(interp, textPtr, argv[4], &index1) != TCL_OK) {
676	    return TCL_ERROR;
677	}
678	if (argc == 5) {
679	    TkTextMakeByteIndex(textPtr->tree, 0, 0, &index2);
680	} else if (TkTextGetIndex(interp, textPtr, argv[5], &index2)
681		!= TCL_OK) {
682	    return TCL_ERROR;
683	}
684
685	/*
686	 * The search below is a bit weird.  The previous toggle can be
687	 * either an on or off toggle. If it is an on toggle, then we
688	 * need to turn around and search forward for the end toggle.
689	 * Otherwise we keep searching backwards.
690	 */
691
692	TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch);
693
694	if (!TkBTreePrevTag(&tSearch)) {
695	    return TCL_OK;
696	}
697	if (tSearch.segPtr->typePtr == &tkTextToggleOnType) {
698	    TkTextPrintIndex(&tSearch.curIndex, position1);
699	    TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
700		    0, &last);
701	    TkBTreeStartSearch(&tSearch.curIndex, &last, tagPtr, &tSearch);
702	    TkBTreeNextTag(&tSearch);
703	    TkTextPrintIndex(&tSearch.curIndex, position2);
704	} else {
705	    TkTextPrintIndex(&tSearch.curIndex, position2);
706	    TkBTreePrevTag(&tSearch);
707	    if (TkTextIndexCmp(&tSearch.curIndex, &index2) < 0) {
708		return TCL_OK;
709	    }
710	    TkTextPrintIndex(&tSearch.curIndex, position1);
711	}
712	Tcl_AppendElement(interp, position1);
713	Tcl_AppendElement(interp, position2);
714    } else if ((c == 'r') && (strncmp(argv[2], "raise", length) == 0)
715	    && (length >= 3)) {
716	TkTextTag *tagPtr2;
717	int prio;
718
719	if ((argc != 4) && (argc != 5)) {
720	    Tcl_AppendResult(interp, "wrong # args: should be \"",
721		    argv[0], " tag raise tagName ?aboveThis?\"",
722		    (char *) NULL);
723	    return TCL_ERROR;
724	}
725	tagPtr = FindTag(interp, textPtr, argv[3]);
726	if (tagPtr == NULL) {
727	    return TCL_ERROR;
728	}
729	if (argc == 5) {
730	    tagPtr2 = FindTag(interp, textPtr, argv[4]);
731	    if (tagPtr2 == NULL) {
732		return TCL_ERROR;
733	    }
734	    if (tagPtr->priority <= tagPtr2->priority) {
735		prio = tagPtr2->priority;
736	    } else {
737		prio = tagPtr2->priority + 1;
738	    }
739	} else {
740	    prio = textPtr->numTags-1;
741	}
742	ChangeTagPriority(textPtr, tagPtr, prio);
743	TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
744		tagPtr, 1);
745    } else if ((c == 'r') && (strncmp(argv[2], "ranges", length) == 0)
746	    && (length >= 3)) {
747	TkTextSearch tSearch;
748	char position[TK_POS_CHARS];
749
750	if (argc != 4) {
751	    Tcl_AppendResult(interp, "wrong # args: should be \"",
752		    argv[0], " tag ranges tagName\"", (char *) NULL);
753	    return TCL_ERROR;
754	}
755	tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]);
756	if (tagPtr == NULL) {
757	    return TCL_OK;
758	}
759	TkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
760	TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
761		0, &last);
762	TkBTreeStartSearch(&first, &last, tagPtr, &tSearch);
763	if (TkBTreeCharTagged(&first, tagPtr)) {
764	    TkTextPrintIndex(&first, position);
765	    Tcl_AppendElement(interp, position);
766	}
767	while (TkBTreeNextTag(&tSearch)) {
768	    TkTextPrintIndex(&tSearch.curIndex, position);
769	    Tcl_AppendElement(interp, position);
770	}
771    } else if ((c == 'r') && (strncmp(argv[2], "remove", length) == 0)
772	    && (length >= 2)) {
773	fullOption = "remove";
774	addTag = 0;
775	goto addAndRemove;
776    } else {
777	Tcl_AppendResult(interp, "bad tag option \"", argv[2],
778		"\": must be add, bind, cget, configure, delete, lower, ",
779		"names, nextrange, raise, ranges, or remove",
780		(char *) NULL);
781	return TCL_ERROR;
782    }
783    return TCL_OK;
784}
785
786/*
787 *----------------------------------------------------------------------
788 *
789 * TkTextCreateTag --
790 *
791 *	Find the record describing a tag within a given text widget,
792 *	creating a new record if one doesn't already exist.
793 *
794 * Results:
795 *	The return value is a pointer to the TkTextTag record for tagName.
796 *
797 * Side effects:
798 *	A new tag record is created if there isn't one already defined
799 *	for tagName.
800 *
801 *----------------------------------------------------------------------
802 */
803
804TkTextTag *
805TkTextCreateTag(textPtr, tagName)
806    TkText *textPtr;		/* Widget in which tag is being used. */
807    CONST char *tagName;	/* Name of desired tag. */
808{
809    register TkTextTag *tagPtr;
810    Tcl_HashEntry *hPtr;
811    int new;
812
813    hPtr = Tcl_CreateHashEntry(&textPtr->tagTable, tagName, &new);
814    if (!new) {
815	return (TkTextTag *) Tcl_GetHashValue(hPtr);
816    }
817
818    /*
819     * No existing entry.  Create a new one, initialize it, and add a
820     * pointer to it to the hash table entry.
821     */
822
823    tagPtr = (TkTextTag *) ckalloc(sizeof(TkTextTag));
824    tagPtr->name = Tcl_GetHashKey(&textPtr->tagTable, hPtr);
825    tagPtr->toggleCount = 0;
826    tagPtr->tagRootPtr = NULL;
827    tagPtr->priority = textPtr->numTags;
828    tagPtr->border = NULL;
829    tagPtr->bdString = NULL;
830    tagPtr->borderWidth = 0;
831    tagPtr->reliefString = NULL;
832    tagPtr->relief = TK_RELIEF_FLAT;
833    tagPtr->bgStipple = None;
834    tagPtr->fgColor = NULL;
835    tagPtr->tkfont = NULL;
836    tagPtr->fgStipple = None;
837    tagPtr->justifyString = NULL;
838    tagPtr->justify = TK_JUSTIFY_LEFT;
839    tagPtr->lMargin1String = NULL;
840    tagPtr->lMargin1 = 0;
841    tagPtr->lMargin2String = NULL;
842    tagPtr->lMargin2 = 0;
843    tagPtr->offsetString = NULL;
844    tagPtr->offset = 0;
845    tagPtr->overstrikeString = NULL;
846    tagPtr->overstrike = 0;
847    tagPtr->rMarginString = NULL;
848    tagPtr->rMargin = 0;
849    tagPtr->spacing1String = NULL;
850    tagPtr->spacing1 = 0;
851    tagPtr->spacing2String = NULL;
852    tagPtr->spacing2 = 0;
853    tagPtr->spacing3String = NULL;
854    tagPtr->spacing3 = 0;
855    tagPtr->tabString = NULL;
856    tagPtr->tabArrayPtr = NULL;
857    tagPtr->underlineString = NULL;
858    tagPtr->underline = 0;
859    tagPtr->elideString = NULL;
860    tagPtr->elide = 0;
861    tagPtr->wrapMode = TEXT_WRAPMODE_NULL;
862    tagPtr->affectsDisplay = 0;
863    textPtr->numTags++;
864    Tcl_SetHashValue(hPtr, tagPtr);
865    return tagPtr;
866}
867
868/*
869 *----------------------------------------------------------------------
870 *
871 * FindTag --
872 *
873 *	See if tag is defined for a given widget.
874 *
875 * Results:
876 *	If tagName is defined in textPtr, a pointer to its TkTextTag
877 *	structure is returned.  Otherwise NULL is returned and an
878 *	error message is recorded in the interp's result unless interp
879 *	is NULL.
880 *
881 * Side effects:
882 *	None.
883 *
884 *----------------------------------------------------------------------
885 */
886
887static TkTextTag *
888FindTag(interp, textPtr, tagName)
889    Tcl_Interp *interp;		/* Interpreter to use for error message;
890				 * if NULL, then don't record an error
891				 * message. */
892    TkText *textPtr;		/* Widget in which tag is being used. */
893    CONST char *tagName;	/* Name of desired tag. */
894{
895    Tcl_HashEntry *hPtr;
896
897    hPtr = Tcl_FindHashEntry(&textPtr->tagTable, tagName);
898    if (hPtr != NULL) {
899	return (TkTextTag *) Tcl_GetHashValue(hPtr);
900    }
901    if (interp != NULL) {
902	Tcl_AppendResult(interp, "tag \"", tagName,
903		"\" isn't defined in text widget", (char *) NULL);
904    }
905    return NULL;
906}
907
908/*
909 *----------------------------------------------------------------------
910 *
911 * TkTextFreeTag --
912 *
913 *	This procedure is called when a tag is deleted to free up the
914 *	memory and other resources associated with the tag.
915 *
916 * Results:
917 *	None.
918 *
919 * Side effects:
920 *	Memory and other resources are freed.
921 *
922 *----------------------------------------------------------------------
923 */
924
925void
926TkTextFreeTag(textPtr, tagPtr)
927    TkText *textPtr;			/* Info about overall widget. */
928    register TkTextTag *tagPtr;		/* Tag being deleted. */
929{
930    if (tagPtr->border != None) {
931	Tk_Free3DBorder(tagPtr->border);
932    }
933    if (tagPtr->bdString != NULL) {
934	ckfree(tagPtr->bdString);
935    }
936    if (tagPtr->reliefString != NULL) {
937	ckfree(tagPtr->reliefString);
938    }
939    if (tagPtr->bgStipple != None) {
940	Tk_FreeBitmap(textPtr->display, tagPtr->bgStipple);
941    }
942    if (tagPtr->fgColor != None) {
943	Tk_FreeColor(tagPtr->fgColor);
944    }
945    Tk_FreeFont(tagPtr->tkfont);
946    if (tagPtr->fgStipple != None) {
947	Tk_FreeBitmap(textPtr->display, tagPtr->fgStipple);
948    }
949    if (tagPtr->justifyString != NULL) {
950	ckfree(tagPtr->justifyString);
951    }
952    if (tagPtr->lMargin1String != NULL) {
953	ckfree(tagPtr->lMargin1String);
954    }
955    if (tagPtr->lMargin2String != NULL) {
956	ckfree(tagPtr->lMargin2String);
957    }
958    if (tagPtr->offsetString != NULL) {
959	ckfree(tagPtr->offsetString);
960    }
961    if (tagPtr->overstrikeString != NULL) {
962	ckfree(tagPtr->overstrikeString);
963    }
964    if (tagPtr->rMarginString != NULL) {
965	ckfree(tagPtr->rMarginString);
966    }
967    if (tagPtr->spacing1String != NULL) {
968	ckfree(tagPtr->spacing1String);
969    }
970    if (tagPtr->spacing2String != NULL) {
971	ckfree(tagPtr->spacing2String);
972    }
973    if (tagPtr->spacing3String != NULL) {
974	ckfree(tagPtr->spacing3String);
975    }
976    if (tagPtr->tabString != NULL) {
977	ckfree(tagPtr->tabString);
978    }
979    if (tagPtr->tabArrayPtr != NULL) {
980	ckfree((char *) tagPtr->tabArrayPtr);
981    }
982    if (tagPtr->underlineString != NULL) {
983	ckfree(tagPtr->underlineString);
984    }
985    ckfree((char *) tagPtr);
986}
987
988/*
989 *----------------------------------------------------------------------
990 *
991 * SortTags --
992 *
993 *	This procedure sorts an array of tag pointers in increasing
994 *	order of priority, optimizing for the common case where the
995 *	array is small.
996 *
997 * Results:
998 *	None.
999 *
1000 * Side effects:
1001 *	None.
1002 *
1003 *----------------------------------------------------------------------
1004 */
1005
1006static void
1007SortTags(numTags, tagArrayPtr)
1008    int numTags;		/* Number of tag pointers at *tagArrayPtr. */
1009    TkTextTag **tagArrayPtr;	/* Pointer to array of pointers. */
1010{
1011    int i, j, prio;
1012    register TkTextTag **tagPtrPtr;
1013    TkTextTag **maxPtrPtr, *tmp;
1014
1015    if (numTags < 2) {
1016	return;
1017    }
1018    if (numTags < 20) {
1019	for (i = numTags-1; i > 0; i--, tagArrayPtr++) {
1020	    maxPtrPtr = tagPtrPtr = tagArrayPtr;
1021	    prio = tagPtrPtr[0]->priority;
1022	    for (j = i, tagPtrPtr++; j > 0; j--, tagPtrPtr++) {
1023		if (tagPtrPtr[0]->priority < prio) {
1024		    prio = tagPtrPtr[0]->priority;
1025		    maxPtrPtr = tagPtrPtr;
1026		}
1027	    }
1028	    tmp = *maxPtrPtr;
1029	    *maxPtrPtr = *tagArrayPtr;
1030	    *tagArrayPtr = tmp;
1031	}
1032    } else {
1033	qsort((VOID *) tagArrayPtr, (unsigned) numTags, sizeof (TkTextTag *),
1034		    TagSortProc);
1035    }
1036}
1037
1038/*
1039 *----------------------------------------------------------------------
1040 *
1041 * TagSortProc --
1042 *
1043 *	This procedure is called by qsort when sorting an array of
1044 *	tags in priority order.
1045 *
1046 * Results:
1047 *	The return value is -1 if the first argument should be before
1048 *	the second element (i.e. it has lower priority), 0 if it's
1049 *	equivalent (this should never happen!), and 1 if it should be
1050 *	after the second element.
1051 *
1052 * Side effects:
1053 *	None.
1054 *
1055 *----------------------------------------------------------------------
1056 */
1057
1058static int
1059TagSortProc(first, second)
1060    CONST VOID *first, *second;		/* Elements to be compared. */
1061{
1062    TkTextTag *tagPtr1, *tagPtr2;
1063
1064    tagPtr1 = * (TkTextTag **) first;
1065    tagPtr2 = * (TkTextTag **) second;
1066    return tagPtr1->priority - tagPtr2->priority;
1067}
1068
1069/*
1070 *----------------------------------------------------------------------
1071 *
1072 * ChangeTagPriority --
1073 *
1074 *	This procedure changes the priority of a tag by modifying
1075 *	its priority and the priorities of other tags that are affected
1076 *	by the change.
1077 *
1078 * Results:
1079 *	None.
1080 *
1081 * Side effects:
1082 *	Priorities may be changed for some or all of the tags in
1083 *	textPtr.  The tags will be arranged so that there is exactly
1084 *	one tag at each priority level between 0 and textPtr->numTags-1,
1085 *	with tagPtr at priority "prio".
1086 *
1087 *----------------------------------------------------------------------
1088 */
1089
1090static void
1091ChangeTagPriority(textPtr, tagPtr, prio)
1092    TkText *textPtr;			/* Information about text widget. */
1093    TkTextTag *tagPtr;			/* Tag whose priority is to be
1094					 * changed. */
1095    int prio;				/* New priority for tag. */
1096{
1097    int low, high, delta;
1098    register TkTextTag *tagPtr2;
1099    Tcl_HashEntry *hPtr;
1100    Tcl_HashSearch search;
1101
1102    if (prio < 0) {
1103	prio = 0;
1104    }
1105    if (prio >= textPtr->numTags) {
1106	prio = textPtr->numTags-1;
1107    }
1108    if (prio == tagPtr->priority) {
1109	return;
1110    } else if (prio < tagPtr->priority) {
1111	low = prio;
1112	high = tagPtr->priority-1;
1113	delta = 1;
1114    } else {
1115	low = tagPtr->priority+1;
1116	high = prio;
1117	delta = -1;
1118    }
1119    for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
1120	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
1121	tagPtr2 = (TkTextTag *) Tcl_GetHashValue(hPtr);
1122	if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) {
1123	    tagPtr2->priority += delta;
1124	}
1125    }
1126    tagPtr->priority = prio;
1127}
1128
1129/*
1130 *--------------------------------------------------------------
1131 *
1132 * TkTextBindProc --
1133 *
1134 *	This procedure is invoked by the Tk dispatcher to handle
1135 *	events associated with bindings on items.
1136 *
1137 * Results:
1138 *	None.
1139 *
1140 * Side effects:
1141 *	Depends on the command invoked as part of the binding
1142 *	(if there was any).
1143 *
1144 *--------------------------------------------------------------
1145 */
1146
1147void
1148TkTextBindProc(clientData, eventPtr)
1149    ClientData clientData;		/* Pointer to canvas structure. */
1150    XEvent *eventPtr;			/* Pointer to X event that just
1151					 * happened. */
1152{
1153    TkText *textPtr = (TkText *) clientData;
1154    int repick  = 0;
1155
1156# define AnyButtonMask (Button1Mask|Button2Mask|Button3Mask\
1157	|Button4Mask|Button5Mask)
1158
1159    Tcl_Preserve((ClientData) textPtr);
1160
1161    /*
1162     * This code simulates grabs for mouse buttons by keeping track
1163     * of whether a button is pressed and refusing to pick a new current
1164     * character while a button is pressed.
1165     */
1166
1167    if (eventPtr->type == ButtonPress) {
1168	textPtr->flags |= BUTTON_DOWN;
1169    } else if (eventPtr->type == ButtonRelease) {
1170	int mask;
1171
1172	switch (eventPtr->xbutton.button) {
1173	    case Button1:
1174		mask = Button1Mask;
1175		break;
1176	    case Button2:
1177		mask = Button2Mask;
1178		break;
1179	    case Button3:
1180		mask = Button3Mask;
1181		break;
1182	    case Button4:
1183		mask = Button4Mask;
1184		break;
1185	    case Button5:
1186		mask = Button5Mask;
1187		break;
1188	    default:
1189		mask = 0;
1190		break;
1191	}
1192	if ((eventPtr->xbutton.state & AnyButtonMask) == (unsigned) mask) {
1193	    textPtr->flags &= ~BUTTON_DOWN;
1194	    repick = 1;
1195	}
1196    } else if ((eventPtr->type == EnterNotify)
1197	    || (eventPtr->type == LeaveNotify)) {
1198	if (eventPtr->xcrossing.state & AnyButtonMask)  {
1199	    textPtr->flags |= BUTTON_DOWN;
1200	} else {
1201	    textPtr->flags &= ~BUTTON_DOWN;
1202	}
1203	TkTextPickCurrent(textPtr, eventPtr);
1204	goto done;
1205    } else if (eventPtr->type == MotionNotify) {
1206	if (eventPtr->xmotion.state & AnyButtonMask)  {
1207	    textPtr->flags |= BUTTON_DOWN;
1208	} else {
1209	    textPtr->flags &= ~BUTTON_DOWN;
1210	}
1211	TkTextPickCurrent(textPtr, eventPtr);
1212    }
1213    if ((textPtr->numCurTags > 0) && (textPtr->bindingTable != NULL)
1214	    && (textPtr->tkwin != NULL)) {
1215	Tk_BindEvent(textPtr->bindingTable, eventPtr, textPtr->tkwin,
1216		textPtr->numCurTags, (ClientData *) textPtr->curTagArrayPtr);
1217    }
1218    if (repick) {
1219	unsigned int oldState;
1220
1221	oldState = eventPtr->xbutton.state;
1222	eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask
1223		|Button3Mask|Button4Mask|Button5Mask);
1224	TkTextPickCurrent(textPtr, eventPtr);
1225	eventPtr->xbutton.state = oldState;
1226    }
1227
1228    done:
1229    Tcl_Release((ClientData) textPtr);
1230}
1231
1232/*
1233 *--------------------------------------------------------------
1234 *
1235 * TkTextPickCurrent --
1236 *
1237 *	Find the character containing the coordinates in an event
1238 *	and place the "current" mark on that character.  If the
1239 *	"current" mark has moved then generate a fake leave event
1240 *	on the old current character and a fake enter event on the new
1241 *	current character.
1242 *
1243 * Results:
1244 *	None.
1245 *
1246 * Side effects:
1247 *	The current mark for textPtr may change.  If it does,
1248 *	then the commands associated with character entry and leave
1249 *	could do just about anything.  For example, the text widget
1250 *	might be deleted.  It is up to the caller to protect itself
1251 *	with calls to Tcl_Preserve and Tcl_Release.
1252 *
1253 *--------------------------------------------------------------
1254 */
1255
1256void
1257TkTextPickCurrent(textPtr, eventPtr)
1258    register TkText *textPtr;		/* Text widget in which to select
1259					 * current character. */
1260    XEvent *eventPtr;			/* Event describing location of
1261					 * mouse cursor.  Must be EnterWindow,
1262					 * LeaveWindow, ButtonRelease, or
1263					 * MotionNotify. */
1264{
1265    TkTextIndex index;
1266    TkTextTag **oldArrayPtr, **newArrayPtr;
1267    TkTextTag **copyArrayPtr = NULL;	/* Initialization needed to prevent
1268					 * compiler warning. */
1269
1270    int numOldTags, numNewTags, i, j, size;
1271    XEvent event;
1272
1273    /*
1274     * If a button is down, then don't do anything at all;  we'll be
1275     * called again when all buttons are up, and we can repick then.
1276     * This implements a form of mouse grabbing.
1277     */
1278
1279    if (textPtr->flags & BUTTON_DOWN) {
1280	if (((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify))
1281		&& ((eventPtr->xcrossing.mode == NotifyGrab)
1282		|| (eventPtr->xcrossing.mode == NotifyUngrab))) {
1283	    /*
1284	     * Special case:  the window is being entered or left because
1285	     * of a grab or ungrab.  In this case, repick after all.
1286	     * Furthermore, clear BUTTON_DOWN to release the simulated
1287	     * grab.
1288	     */
1289
1290	    textPtr->flags &= ~BUTTON_DOWN;
1291	} else {
1292	    return;
1293	}
1294    }
1295
1296    /*
1297     * Save information about this event in the widget in case we have
1298     * to synthesize more enter and leave events later (e.g. because a
1299     * character was deleted, causing a new character to be underneath
1300     * the mouse cursor).  Also translate MotionNotify events into
1301     * EnterNotify events, since that's what gets reported to event
1302     * handlers when the current character changes.
1303     */
1304
1305    if (eventPtr != &textPtr->pickEvent) {
1306	if ((eventPtr->type == MotionNotify)
1307		|| (eventPtr->type == ButtonRelease)) {
1308	    textPtr->pickEvent.xcrossing.type = EnterNotify;
1309	    textPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial;
1310	    textPtr->pickEvent.xcrossing.send_event
1311		    = eventPtr->xmotion.send_event;
1312	    textPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display;
1313	    textPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window;
1314	    textPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root;
1315	    textPtr->pickEvent.xcrossing.subwindow = None;
1316	    textPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time;
1317	    textPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x;
1318	    textPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y;
1319	    textPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root;
1320	    textPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root;
1321	    textPtr->pickEvent.xcrossing.mode = NotifyNormal;
1322	    textPtr->pickEvent.xcrossing.detail = NotifyNonlinear;
1323	    textPtr->pickEvent.xcrossing.same_screen
1324		    = eventPtr->xmotion.same_screen;
1325	    textPtr->pickEvent.xcrossing.focus = False;
1326	    textPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state;
1327	} else  {
1328	    textPtr->pickEvent = *eventPtr;
1329	}
1330    }
1331
1332    /*
1333     * Find the new current character, then find and sort all of the
1334     * tags associated with it.
1335     */
1336
1337    if (textPtr->pickEvent.type != LeaveNotify) {
1338	TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x,
1339		textPtr->pickEvent.xcrossing.y, &index);
1340	newArrayPtr = TkBTreeGetTags(&index, &numNewTags);
1341	SortTags(numNewTags, newArrayPtr);
1342    } else {
1343	newArrayPtr = NULL;
1344	numNewTags = 0;
1345    }
1346
1347    /*
1348     * Resort the tags associated with the previous marked character
1349     * (the priorities might have changed), then make a copy of the
1350     * new tags, and compare the old tags to the copy, nullifying
1351     * any tags that are present in both groups (i.e. the tags that
1352     * haven't changed).
1353     */
1354
1355    SortTags(textPtr->numCurTags, textPtr->curTagArrayPtr);
1356    if (numNewTags > 0) {
1357	size = numNewTags * sizeof(TkTextTag *);
1358	copyArrayPtr = (TkTextTag **) ckalloc((unsigned) size);
1359	memcpy((VOID *) copyArrayPtr, (VOID *) newArrayPtr, (size_t) size);
1360	for (i = 0; i < textPtr->numCurTags; i++) {
1361	    for (j = 0; j < numNewTags; j++) {
1362		if (textPtr->curTagArrayPtr[i] == copyArrayPtr[j]) {
1363		    textPtr->curTagArrayPtr[i] = NULL;
1364		    copyArrayPtr[j] = NULL;
1365		    break;
1366		}
1367	    }
1368	}
1369    }
1370
1371    /*
1372     * Invoke the binding system with a LeaveNotify event for all of
1373     * the tags that have gone away.  We have to be careful here,
1374     * because it's possible that the binding could do something
1375     * (like calling tkwait) that eventually modifies
1376     * textPtr->curTagArrayPtr.  To avoid problems in situations like
1377     * this, update curTagArrayPtr to its new value before invoking
1378     * any bindings, and don't use it any more here.
1379     */
1380
1381    numOldTags = textPtr->numCurTags;
1382    textPtr->numCurTags = numNewTags;
1383    oldArrayPtr = textPtr->curTagArrayPtr;
1384    textPtr->curTagArrayPtr = newArrayPtr;
1385    if (numOldTags != 0) {
1386	if ((textPtr->bindingTable != NULL) && (textPtr->tkwin != NULL)) {
1387	    event = textPtr->pickEvent;
1388	    event.type = LeaveNotify;
1389
1390	    /*
1391	     * Always use a detail of NotifyAncestor.  Besides being
1392	     * consistent, this avoids problems where the binding code
1393	     * will discard NotifyInferior events.
1394	     */
1395
1396	    event.xcrossing.detail = NotifyAncestor;
1397	    Tk_BindEvent(textPtr->bindingTable, &event, textPtr->tkwin,
1398		    numOldTags, (ClientData *) oldArrayPtr);
1399	}
1400	ckfree((char *) oldArrayPtr);
1401    }
1402
1403    /*
1404     * Reset the "current" mark (be careful to recompute its location,
1405     * since it might have changed during an event binding).  Then
1406     * invoke the binding system with an EnterNotify event for all of
1407     * the tags that have just appeared.
1408     */
1409
1410    TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x,
1411	    textPtr->pickEvent.xcrossing.y, &index);
1412    TkTextSetMark(textPtr, "current", &index);
1413    if (numNewTags != 0) {
1414	if ((textPtr->bindingTable != NULL) && (textPtr->tkwin != NULL)) {
1415	    event = textPtr->pickEvent;
1416	    event.type = EnterNotify;
1417	    event.xcrossing.detail = NotifyAncestor;
1418	    Tk_BindEvent(textPtr->bindingTable, &event, textPtr->tkwin,
1419		    numNewTags, (ClientData *) copyArrayPtr);
1420	}
1421	ckfree((char *) copyArrayPtr);
1422    }
1423}
1424