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