]>
Commit | Line | Data |
---|---|---|
6a5fa4e0 MG |
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 1992 Regents of the University of California. | |
9 | * Permission to use, copy, modify, and distribute this | |
10 | * software and its documentation for any purpose and without | |
11 | * fee is hereby granted, provided that the above copyright | |
12 | * notice appear in all copies. The University of California | |
13 | * makes no representations about the suitability of this | |
14 | * software for any purpose. It is provided "as is" without | |
15 | * express or implied warranty. | |
16 | */ | |
17 | ||
18 | #ifndef lint | |
19 | static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextTag.c,v 1.3 92/07/28 15:38:59 ouster Exp $ SPRITE (Berkeley)"; | |
20 | #endif | |
21 | ||
22 | #include "default.h" | |
23 | #include "tkconfig.h" | |
24 | #include "tk.h" | |
25 | #include "tktext.h" | |
26 | ||
27 | /* | |
28 | * Information used for parsing tag configuration information: | |
29 | */ | |
30 | ||
31 | static Tk_ConfigSpec tagConfigSpecs[] = { | |
32 | {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL, | |
33 | (char *) NULL, Tk_Offset(TkTextTag, border), TK_CONFIG_NULL_OK}, | |
34 | {TK_CONFIG_BITMAP, "-bgstipple", (char *) NULL, (char *) NULL, | |
35 | (char *) NULL, Tk_Offset(TkTextTag, bgStipple), TK_CONFIG_NULL_OK}, | |
36 | {TK_CONFIG_PIXELS, "-borderwidth", (char *) NULL, (char *) NULL, | |
37 | "0", Tk_Offset(TkTextTag, borderWidth), TK_CONFIG_DONT_SET_DEFAULT}, | |
38 | {TK_CONFIG_BITMAP, "-fgstipple", (char *) NULL, (char *) NULL, | |
39 | (char *) NULL, Tk_Offset(TkTextTag, fgStipple), TK_CONFIG_NULL_OK}, | |
40 | {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL, | |
41 | (char *) NULL, Tk_Offset(TkTextTag, fontPtr), TK_CONFIG_NULL_OK}, | |
42 | {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL, | |
43 | (char *) NULL, Tk_Offset(TkTextTag, fgColor), TK_CONFIG_NULL_OK}, | |
44 | {TK_CONFIG_RELIEF, "-relief", (char *) NULL, (char *) NULL, | |
45 | "flat", Tk_Offset(TkTextTag, relief), TK_CONFIG_DONT_SET_DEFAULT}, | |
46 | {TK_CONFIG_BOOLEAN, "-underline", (char *) NULL, (char *) NULL, | |
47 | "false", Tk_Offset(TkTextTag, underline), TK_CONFIG_DONT_SET_DEFAULT}, | |
48 | {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, | |
49 | (char *) NULL, 0, 0} | |
50 | }; | |
51 | ||
52 | ||
53 | /* | |
54 | * The following definition specifies the maximum number of characters | |
55 | * needed in a string to hold a position specifier. | |
56 | */ | |
57 | ||
58 | #define POS_CHARS 30 | |
59 | ||
60 | /* | |
61 | * Forward declarations for procedures defined later in this file: | |
62 | */ | |
63 | ||
64 | static void ChangeTagPriority _ANSI_ARGS_((TkText *textPtr, | |
65 | TkTextTag *tagPtr, int prio)); | |
66 | static TkTextTag * FindTag _ANSI_ARGS_((Tcl_Interp *interp, | |
67 | TkText *textPtr, char *tagName)); | |
68 | static void SortTags _ANSI_ARGS_((int numTags, | |
69 | TkTextTag **tagArrayPtr)); | |
70 | static int TagSortProc _ANSI_ARGS_((CONST VOID *first, | |
71 | CONST VOID *second)); | |
72 | static void TextDoEvent _ANSI_ARGS_((TkText *textPtr, | |
73 | XEvent *eventPtr)); | |
74 | \f | |
75 | /* | |
76 | *-------------------------------------------------------------- | |
77 | * | |
78 | * TkTextTagCmd -- | |
79 | * | |
80 | * This procedure is invoked to process the "tag" options of | |
81 | * the widget command for text widgets. See the user documentation | |
82 | * for details on what it does. | |
83 | * | |
84 | * Results: | |
85 | * A standard Tcl result. | |
86 | * | |
87 | * Side effects: | |
88 | * See the user documentation. | |
89 | * | |
90 | *-------------------------------------------------------------- | |
91 | */ | |
92 | ||
93 | int | |
94 | TkTextTagCmd(textPtr, interp, argc, argv) | |
95 | register TkText *textPtr; /* Information about text widget. */ | |
96 | Tcl_Interp *interp; /* Current interpreter. */ | |
97 | int argc; /* Number of arguments. */ | |
98 | char **argv; /* Argument strings. Someone else has already | |
99 | * parsed this command enough to know that | |
100 | * argv[1] is "tag". */ | |
101 | { | |
102 | int length, line1, ch1, line2, ch2, i, addTag; | |
103 | char c; | |
104 | char *fullOption; | |
105 | register TkTextTag *tagPtr; | |
106 | ||
107 | if (argc < 3) { | |
108 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
109 | argv[0], " tag option ?arg arg ...?\"", (char *) NULL); | |
110 | return TCL_ERROR; | |
111 | } | |
112 | c = argv[2][0]; | |
113 | length = strlen(argv[2]); | |
114 | if ((c == 'a') && (strncmp(argv[2], "add", length) == 0)) { | |
115 | fullOption = "add"; | |
116 | addTag = 1; | |
117 | ||
118 | addAndRemove: | |
119 | if ((argc != 5) && (argc != 6)) { | |
120 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
121 | argv[0], " tag ", fullOption, " tagName index1 ?index2?\"", | |
122 | (char *) NULL); | |
123 | return TCL_ERROR; | |
124 | } | |
125 | tagPtr = TkTextCreateTag(textPtr, argv[3]); | |
126 | if (TkTextGetIndex(interp, textPtr, argv[4], &line1, &ch1) != TCL_OK) { | |
127 | return TCL_ERROR; | |
128 | } | |
129 | if (argc == 6) { | |
130 | if (TkTextGetIndex(interp, textPtr, argv[5], &line2, &ch2) | |
131 | != TCL_OK) { | |
132 | return TCL_ERROR; | |
133 | } | |
134 | } else { | |
135 | line2 = line1; | |
136 | ch2 = ch1+1; | |
137 | } | |
138 | if (TK_TAG_AFFECTS_DISPLAY(tagPtr)) { | |
139 | TkTextRedrawTag(textPtr, line1, ch1, line2, ch2, tagPtr, !addTag); | |
140 | } | |
141 | TkBTreeTag(textPtr->tree, line1, ch1, line2, ch2, tagPtr, addTag); | |
142 | ||
143 | /* | |
144 | * If the tag is "sel" then grab the selection if we're supposed | |
145 | * to export it and don't already have it. Also, invalidate | |
146 | * partially-completed selection retrievals. | |
147 | */ | |
148 | ||
149 | if (tagPtr == textPtr->selTagPtr) { | |
150 | if (addTag && textPtr->exportSelection | |
151 | && !(textPtr->flags & GOT_SELECTION)) { | |
152 | Tk_OwnSelection(textPtr->tkwin, TkTextLostSelection, | |
153 | (ClientData) textPtr); | |
154 | textPtr->flags |= GOT_SELECTION; | |
155 | } | |
156 | textPtr->selOffset = -1; | |
157 | } | |
158 | } else if ((c == 'b') && (strncmp(argv[2], "bind", length) == 0)) { | |
159 | if ((argc < 4) || (argc > 6)) { | |
160 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
161 | argv[0], " tag bind tagName ?sequence? ?command?\"", | |
162 | (char *) NULL); | |
163 | return TCL_ERROR; | |
164 | } | |
165 | tagPtr = TkTextCreateTag(textPtr, argv[3]); | |
166 | ||
167 | /* | |
168 | * Make a binding table if the widget doesn't already have | |
169 | * one. | |
170 | */ | |
171 | ||
172 | if (textPtr->bindingTable == NULL) { | |
173 | textPtr->bindingTable = Tk_CreateBindingTable(interp); | |
174 | } | |
175 | ||
176 | if (argc == 6) { | |
177 | int append = 0; | |
178 | unsigned long mask; | |
179 | ||
180 | if (argv[5][0] == 0) { | |
181 | return Tk_DeleteBinding(interp, textPtr->bindingTable, | |
182 | (ClientData) tagPtr, argv[4]); | |
183 | } | |
184 | if (argv[5][0] == '+') { | |
185 | argv[5]++; | |
186 | append = 1; | |
187 | } | |
188 | mask = Tk_CreateBinding(interp, textPtr->bindingTable, | |
189 | (ClientData) tagPtr, argv[4], argv[5], append); | |
190 | if (mask == 0) { | |
191 | return TCL_ERROR; | |
192 | } | |
193 | if (mask & ~(ButtonMotionMask|Button1MotionMask|Button2MotionMask | |
194 | |Button3MotionMask|Button4MotionMask|Button5MotionMask | |
195 | |ButtonPressMask|ButtonReleaseMask|EnterWindowMask | |
196 | |LeaveWindowMask|KeyPressMask|KeyReleaseMask | |
197 | |PointerMotionMask)) { | |
198 | Tk_DeleteBinding(interp, textPtr->bindingTable, | |
199 | (ClientData) tagPtr, argv[4]); | |
200 | Tcl_ResetResult(interp); | |
201 | Tcl_AppendResult(interp, "requested illegal events; ", | |
202 | "only key, button, motion, and enter/leave ", | |
203 | "events may be used", (char *) NULL); | |
204 | return TCL_ERROR; | |
205 | } | |
206 | } else if (argc == 5) { | |
207 | char *command; | |
208 | ||
209 | command = Tk_GetBinding(interp, textPtr->bindingTable, | |
210 | (ClientData) tagPtr, argv[4]); | |
211 | if (command == NULL) { | |
212 | return TCL_ERROR; | |
213 | } | |
214 | interp->result = command; | |
215 | } else { | |
216 | Tk_GetAllBindings(interp, textPtr->bindingTable, | |
217 | (ClientData) tagPtr); | |
218 | } | |
219 | } else if ((c == 'c') && (strncmp(argv[2], "configure", length) == 0)) { | |
220 | if (argc < 4) { | |
221 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
222 | argv[0], " tag configure tagName ?option? ?value? ", | |
223 | "?option value ...?\"", (char *) NULL); | |
224 | return TCL_ERROR; | |
225 | } | |
226 | tagPtr = TkTextCreateTag(textPtr, argv[3]); | |
227 | if (argc == 4) { | |
228 | return Tk_ConfigureInfo(interp, textPtr->tkwin, tagConfigSpecs, | |
229 | (char *) tagPtr, (char *) NULL, 0); | |
230 | } else if (argc == 5) { | |
231 | return Tk_ConfigureInfo(interp, textPtr->tkwin, tagConfigSpecs, | |
232 | (char *) tagPtr, argv[4], 0); | |
233 | } else { | |
234 | int result; | |
235 | ||
236 | result = Tk_ConfigureWidget(interp, textPtr->tkwin, tagConfigSpecs, | |
237 | argc-4, argv+4, (char *) tagPtr, 0); | |
238 | /* | |
239 | * If the "sel" tag was changed, be sure to mirror information | |
240 | * from the tag back into the text widget record. NOTE: we | |
241 | * don't have to free up information in the widget record | |
242 | * before overwriting it, because it was mirrored in the tag | |
243 | * and hence freed when the tag field was overwritten. | |
244 | */ | |
245 | ||
246 | if (tagPtr == textPtr->selTagPtr) { | |
247 | textPtr->selBorder = tagPtr->border; | |
248 | textPtr->selBorderWidth = tagPtr->borderWidth; | |
249 | textPtr->selFgColorPtr = tagPtr->fgColor; | |
250 | } | |
251 | TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree), | |
252 | 0, tagPtr, 1); | |
253 | return result; | |
254 | } | |
255 | } else if ((c == 'd') && (strncmp(argv[2], "delete", length) == 0)) { | |
256 | Tcl_HashEntry *hPtr; | |
257 | ||
258 | if (argc < 4) { | |
259 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
260 | argv[0], " tag delete tagName tagName ...\"", | |
261 | (char *) NULL); | |
262 | return TCL_ERROR; | |
263 | } | |
264 | for (i = 3; i < argc; i++) { | |
265 | hPtr = Tcl_FindHashEntry(&textPtr->tagTable, argv[i]); | |
266 | if (hPtr == NULL) { | |
267 | continue; | |
268 | } | |
269 | tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); | |
270 | if (tagPtr == textPtr->selTagPtr) { | |
271 | interp->result = "can't delete selection tag"; | |
272 | return TCL_ERROR; | |
273 | } | |
274 | if (TK_TAG_AFFECTS_DISPLAY(tagPtr)) { | |
275 | TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree), | |
276 | 0, tagPtr, 1); | |
277 | } | |
278 | TkBTreeTag(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree), | |
279 | 0, tagPtr, 0); | |
280 | Tcl_DeleteHashEntry(hPtr); | |
281 | if (textPtr->bindingTable != NULL) { | |
282 | Tk_DeleteAllBindings(textPtr->bindingTable, | |
283 | (ClientData) tagPtr); | |
284 | } | |
285 | ||
286 | /* | |
287 | * Update the tag priorities to reflect the deletion of this tag. | |
288 | */ | |
289 | ||
290 | ChangeTagPriority(textPtr, tagPtr, textPtr->numTags-1); | |
291 | textPtr->numTags -= 1; | |
292 | TkTextFreeTag(tagPtr); | |
293 | } | |
294 | } else if ((c == 'l') && (strncmp(argv[2], "lower", length) == 0)) { | |
295 | TkTextTag *tagPtr2; | |
296 | int prio; | |
297 | ||
298 | if ((argc != 4) && (argc != 5)) { | |
299 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
300 | argv[0], " tag lower tagName ?belowThis?\"", | |
301 | (char *) NULL); | |
302 | return TCL_ERROR; | |
303 | } | |
304 | tagPtr = FindTag(interp, textPtr, argv[3]); | |
305 | if (tagPtr == NULL) { | |
306 | return TCL_ERROR; | |
307 | } | |
308 | if (argc == 5) { | |
309 | tagPtr2 = FindTag(interp, textPtr, argv[4]); | |
310 | if (tagPtr2 == NULL) { | |
311 | return TCL_ERROR; | |
312 | } | |
313 | if (tagPtr->priority < tagPtr2->priority) { | |
314 | prio = tagPtr2->priority - 1; | |
315 | } else { | |
316 | prio = tagPtr2->priority; | |
317 | } | |
318 | } else { | |
319 | prio = 0; | |
320 | } | |
321 | ChangeTagPriority(textPtr, tagPtr, prio); | |
322 | TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree), | |
323 | 0, tagPtr, 1); | |
324 | } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0) | |
325 | && (length >= 2)) { | |
326 | TkTextTag **arrayPtr; | |
327 | int arraySize; | |
328 | TkTextLine *linePtr; | |
329 | ||
330 | if ((argc != 3) && (argc != 4)) { | |
331 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
332 | argv[0], " tag names ?index?\"", | |
333 | (char *) NULL); | |
334 | return TCL_ERROR; | |
335 | } | |
336 | if (argc == 3) { | |
337 | Tcl_HashSearch search; | |
338 | Tcl_HashEntry *hPtr; | |
339 | ||
340 | arrayPtr = (TkTextTag **) ckalloc((unsigned) | |
341 | (textPtr->numTags * sizeof(TkTextTag *))); | |
342 | for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); | |
343 | hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { | |
344 | arrayPtr[i] = (TkTextTag *) Tcl_GetHashValue(hPtr); | |
345 | } | |
346 | arraySize = textPtr->numTags; | |
347 | } else { | |
348 | if (TkTextGetIndex(interp, textPtr, argv[3], &line1, &ch1) | |
349 | != TCL_OK) { | |
350 | return TCL_ERROR; | |
351 | } | |
352 | linePtr = TkBTreeFindLine(textPtr->tree, line1); | |
353 | if (linePtr == NULL) { | |
354 | return TCL_OK; | |
355 | } | |
356 | arrayPtr = TkBTreeGetTags(textPtr->tree, linePtr, ch1, &arraySize); | |
357 | if (arrayPtr == NULL) { | |
358 | return TCL_OK; | |
359 | } | |
360 | } | |
361 | SortTags(arraySize, arrayPtr); | |
362 | for (i = 0; i < arraySize; i++) { | |
363 | tagPtr = arrayPtr[i]; | |
364 | Tcl_AppendElement(interp, tagPtr->name, 0); | |
365 | } | |
366 | ckfree((char *) arrayPtr); | |
367 | } else if ((c == 'n') && (strncmp(argv[2], "nextrange", length) == 0) | |
368 | && (length >= 2)) { | |
369 | TkTextSearch tSearch; | |
370 | char position[POS_CHARS]; | |
371 | ||
372 | if ((argc != 5) && (argc != 6)) { | |
373 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
374 | argv[0], " tag nextrange tagName index1 ?index2?\"", | |
375 | (char *) NULL); | |
376 | return TCL_ERROR; | |
377 | } | |
378 | tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]); | |
379 | if (tagPtr == NULL) { | |
380 | return TCL_OK; | |
381 | } | |
382 | if (TkTextGetIndex(interp, textPtr, argv[4], &line1, &ch1) != TCL_OK) { | |
383 | return TCL_ERROR; | |
384 | } | |
385 | if (argc == 5) { | |
386 | line2 = TkBTreeNumLines(textPtr->tree); | |
387 | ch2 = 0; | |
388 | } else if (TkTextGetIndex(interp, textPtr, argv[5], &line2, &ch2) | |
389 | != TCL_OK) { | |
390 | return TCL_ERROR; | |
391 | } | |
392 | ||
393 | /* | |
394 | * The search below is a bit tricky. Rather than use the B-tree | |
395 | * facilities to stop the search at line2.ch2, let it search up | |
396 | * until the end of the file but check for a position past line2.ch2 | |
397 | * ourselves. The reason for doing it this way is that we only | |
398 | * care whether the *start* of the range is before line2.ch2; once | |
399 | * we find the start, we don't want TkBTreeNextTag to abort the | |
400 | * search because the end of the range is after line2.ch2. | |
401 | */ | |
402 | ||
403 | TkBTreeStartSearch(textPtr->tree, line1, ch1, | |
404 | TkBTreeNumLines(textPtr->tree), 0, tagPtr, &tSearch); | |
405 | if (!TkBTreeNextTag(&tSearch)) { | |
406 | return TCL_OK; | |
407 | } | |
408 | if (!TkBTreeCharTagged(tSearch.linePtr, tSearch.ch1, tagPtr)) { | |
409 | if (!TkBTreeNextTag(&tSearch)) { | |
410 | return TCL_OK; | |
411 | } | |
412 | } | |
413 | if ((tSearch.line1 > line2) || ((tSearch.line1 == line2) | |
414 | && (tSearch.ch1 >= ch2))) { | |
415 | return TCL_OK; | |
416 | } | |
417 | TkTextPrintIndex(tSearch.line1, tSearch.ch1, position); | |
418 | Tcl_AppendElement(interp, position, 0); | |
419 | TkBTreeNextTag(&tSearch); | |
420 | TkTextPrintIndex(tSearch.line1, tSearch.ch1, position); | |
421 | Tcl_AppendElement(interp, position, 0); | |
422 | } else if ((c == 'r') && (strncmp(argv[2], "raise", length) == 0) | |
423 | && (length >= 3)) { | |
424 | TkTextTag *tagPtr2; | |
425 | int prio; | |
426 | ||
427 | if ((argc != 4) && (argc != 5)) { | |
428 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
429 | argv[0], " tag raise tagName ?aboveThis?\"", | |
430 | (char *) NULL); | |
431 | return TCL_ERROR; | |
432 | } | |
433 | tagPtr = FindTag(interp, textPtr, argv[3]); | |
434 | if (tagPtr == NULL) { | |
435 | return TCL_ERROR; | |
436 | } | |
437 | if (argc == 5) { | |
438 | tagPtr2 = FindTag(interp, textPtr, argv[4]); | |
439 | if (tagPtr2 == NULL) { | |
440 | return TCL_ERROR; | |
441 | } | |
442 | if (tagPtr->priority <= tagPtr2->priority) { | |
443 | prio = tagPtr2->priority; | |
444 | } else { | |
445 | prio = tagPtr2->priority + 1; | |
446 | } | |
447 | } else { | |
448 | prio = textPtr->numTags-1; | |
449 | } | |
450 | ChangeTagPriority(textPtr, tagPtr, prio); | |
451 | TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree), | |
452 | 0, tagPtr, 1); | |
453 | } else if ((c == 'r') && (strncmp(argv[2], "ranges", length) == 0) | |
454 | && (length >= 3)) { | |
455 | TkTextSearch tSearch; | |
456 | char position[POS_CHARS]; | |
457 | ||
458 | if (argc != 4) { | |
459 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
460 | argv[0], " tag ranges tagName\"", (char *) NULL); | |
461 | return TCL_ERROR; | |
462 | } | |
463 | tagPtr = FindTag((Tcl_Interp *) NULL, textPtr, argv[3]); | |
464 | if (tagPtr == NULL) { | |
465 | return TCL_OK; | |
466 | } | |
467 | TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree), | |
468 | 0, tagPtr, &tSearch); | |
469 | while (TkBTreeNextTag(&tSearch)) { | |
470 | TkTextPrintIndex(tSearch.line1, tSearch.ch1, position); | |
471 | Tcl_AppendElement(interp, position, 0); | |
472 | } | |
473 | } else if ((c == 'r') && (strncmp(argv[2], "remove", length) == 0) | |
474 | && (length >= 2)) { | |
475 | fullOption = "remove"; | |
476 | addTag = 0; | |
477 | goto addAndRemove; | |
478 | } else { | |
479 | Tcl_AppendResult(interp, "bad tag option \"", argv[2], | |
480 | "\": must be add, bind, configure, delete, lower, ", | |
481 | "names, nextrange, raise, ranges, or remove", | |
482 | (char *) NULL); | |
483 | return TCL_ERROR; | |
484 | } | |
485 | return TCL_OK; | |
486 | } | |
487 | \f | |
488 | /* | |
489 | *---------------------------------------------------------------------- | |
490 | * | |
491 | * TkTextCreateTag -- | |
492 | * | |
493 | * Find the record describing a tag within a given text widget, | |
494 | * creating a new record if one doesn't already exist. | |
495 | * | |
496 | * Results: | |
497 | * The return value is a pointer to the TkTextTag record for tagName. | |
498 | * | |
499 | * Side effects: | |
500 | * A new tag record is created if there isn't one already defined | |
501 | * for tagName. | |
502 | * | |
503 | *---------------------------------------------------------------------- | |
504 | */ | |
505 | ||
506 | TkTextTag * | |
507 | TkTextCreateTag(textPtr, tagName) | |
508 | TkText *textPtr; /* Widget in which tag is being used. */ | |
509 | char *tagName; /* Name of desired tag. */ | |
510 | { | |
511 | register TkTextTag *tagPtr; | |
512 | Tcl_HashEntry *hPtr; | |
513 | int new; | |
514 | ||
515 | hPtr = Tcl_CreateHashEntry(&textPtr->tagTable, tagName, &new); | |
516 | if (!new) { | |
517 | return (TkTextTag *) Tcl_GetHashValue(hPtr); | |
518 | } | |
519 | ||
520 | /* | |
521 | * No existing entry. Create a new one, initialize it, and add a | |
522 | * pointer to it to the hash table entry. | |
523 | */ | |
524 | ||
525 | tagPtr = (TkTextTag *) ckalloc(sizeof(TkTextTag)); | |
526 | tagPtr->name = Tcl_GetHashKey(&textPtr->tagTable, hPtr); | |
527 | tagPtr->priority = textPtr->numTags; | |
528 | tagPtr->border = NULL; | |
529 | tagPtr->borderWidth = 1; | |
530 | tagPtr->relief = TK_RELIEF_FLAT; | |
531 | tagPtr->bgStipple = None; | |
532 | tagPtr->fgColor = NULL; | |
533 | tagPtr->fontPtr = NULL; | |
534 | tagPtr->fgStipple = None; | |
535 | tagPtr->underline = 0; | |
536 | textPtr->numTags++; | |
537 | Tcl_SetHashValue(hPtr, tagPtr); | |
538 | return tagPtr; | |
539 | } | |
540 | \f | |
541 | /* | |
542 | *---------------------------------------------------------------------- | |
543 | * | |
544 | * FindTag -- | |
545 | * | |
546 | * See if tag is defined for a given widget. | |
547 | * | |
548 | * Results: | |
549 | * If tagName is defined in textPtr, a pointer to its TkTextTag | |
550 | * structure is returned. Otherwise NULL is returned and an | |
551 | * error message is recorded in interp->result unless interp | |
552 | * is NULL. | |
553 | * | |
554 | * Side effects: | |
555 | * None. | |
556 | * | |
557 | *---------------------------------------------------------------------- | |
558 | */ | |
559 | ||
560 | static TkTextTag * | |
561 | FindTag(interp, textPtr, tagName) | |
562 | Tcl_Interp *interp; /* Interpreter to use for error message; | |
563 | * if NULL, then don't record an error | |
564 | * message. */ | |
565 | TkText *textPtr; /* Widget in which tag is being used. */ | |
566 | char *tagName; /* Name of desired tag. */ | |
567 | { | |
568 | Tcl_HashEntry *hPtr; | |
569 | ||
570 | hPtr = Tcl_FindHashEntry(&textPtr->tagTable, tagName); | |
571 | if (hPtr != NULL) { | |
572 | return (TkTextTag *) Tcl_GetHashValue(hPtr); | |
573 | } | |
574 | if (interp != NULL) { | |
575 | Tcl_AppendResult(interp, "tag \"", tagName, | |
576 | "\" isn't defined in text widget", (char *) NULL); | |
577 | } | |
578 | return NULL; | |
579 | } | |
580 | \f | |
581 | /* | |
582 | *---------------------------------------------------------------------- | |
583 | * | |
584 | * TkTextFreeTag -- | |
585 | * | |
586 | * This procedure is called when a tag is deleted to free up the | |
587 | * memory and other resources associated with the tag. | |
588 | * | |
589 | * Results: | |
590 | * None. | |
591 | * | |
592 | * Side effects: | |
593 | * Memory and other resources are freed. | |
594 | * | |
595 | *---------------------------------------------------------------------- | |
596 | */ | |
597 | ||
598 | void | |
599 | TkTextFreeTag(tagPtr) | |
600 | register TkTextTag *tagPtr; /* Tag being deleted. */ | |
601 | { | |
602 | if (tagPtr->border != None) { | |
603 | Tk_Free3DBorder(tagPtr->border); | |
604 | } | |
605 | if (tagPtr->bgStipple != None) { | |
606 | Tk_FreeBitmap(tagPtr->bgStipple); | |
607 | } | |
608 | if (tagPtr->fgColor != None) { | |
609 | Tk_FreeColor(tagPtr->fgColor); | |
610 | } | |
611 | if (tagPtr->fgStipple != None) { | |
612 | Tk_FreeBitmap(tagPtr->fgStipple); | |
613 | } | |
614 | ckfree((char *) tagPtr); | |
615 | } | |
616 | \f | |
617 | /* | |
618 | *---------------------------------------------------------------------- | |
619 | * | |
620 | * SortTags -- | |
621 | * | |
622 | * This procedure sorts an array of tag pointers in increasing | |
623 | * order of priority, optimizing for the common case where the | |
624 | * array is small. | |
625 | * | |
626 | * Results: | |
627 | * None. | |
628 | * | |
629 | * Side effects: | |
630 | * None. | |
631 | * | |
632 | *---------------------------------------------------------------------- | |
633 | */ | |
634 | ||
635 | static void | |
636 | SortTags(numTags, tagArrayPtr) | |
637 | int numTags; /* Number of tag pointers at *tagArrayPtr. */ | |
638 | TkTextTag **tagArrayPtr; /* Pointer to array of pointers. */ | |
639 | { | |
640 | int i, j, prio; | |
641 | register TkTextTag **tagPtrPtr; | |
642 | TkTextTag **maxPtrPtr, *tmp; | |
643 | ||
644 | if (numTags < 2) { | |
645 | return; | |
646 | } | |
647 | if (numTags < 20) { | |
648 | for (i = numTags-1; i > 0; i--, tagArrayPtr++) { | |
649 | maxPtrPtr = tagPtrPtr = tagArrayPtr; | |
650 | prio = tagPtrPtr[0]->priority; | |
651 | for (j = i, tagPtrPtr++; j > 0; j--, tagPtrPtr++) { | |
652 | if (tagPtrPtr[0]->priority < prio) { | |
653 | prio = tagPtrPtr[0]->priority; | |
654 | maxPtrPtr = tagPtrPtr; | |
655 | } | |
656 | } | |
657 | tmp = *maxPtrPtr; | |
658 | *maxPtrPtr = *tagArrayPtr; | |
659 | *tagArrayPtr = tmp; | |
660 | } | |
661 | } else { | |
662 | qsort((VOID *) tagArrayPtr, numTags, sizeof (TkTextTag *), | |
663 | TagSortProc); | |
664 | } | |
665 | } | |
666 | \f | |
667 | /* | |
668 | *---------------------------------------------------------------------- | |
669 | * | |
670 | * TagSortProc -- | |
671 | * | |
672 | * This procedure is called by qsort when sorting an array of | |
673 | * tags in priority order. | |
674 | * | |
675 | * Results: | |
676 | * The return value is -1 if the first argument should be before | |
677 | * the second element (i.e. it has lower priority), 0 if it's | |
678 | * equivalent (this should never happen!), and 1 if it should be | |
679 | * after the second element. | |
680 | * | |
681 | * Side effects: | |
682 | * None. | |
683 | * | |
684 | *---------------------------------------------------------------------- | |
685 | */ | |
686 | ||
687 | static int | |
688 | TagSortProc(first, second) | |
689 | CONST VOID *first, *second; /* Elements to be compared. */ | |
690 | { | |
691 | TkTextTag *tagPtr1, *tagPtr2; | |
692 | ||
693 | tagPtr1 = * (TkTextTag **) first; | |
694 | tagPtr2 = * (TkTextTag **) second; | |
695 | return tagPtr1->priority - tagPtr2->priority; | |
696 | } | |
697 | \f | |
698 | /* | |
699 | *---------------------------------------------------------------------- | |
700 | * | |
701 | * ChangeTagPriority -- | |
702 | * | |
703 | * This procedure changes the priority of a tag by modifying | |
704 | * its priority and all other ones whose priority is affected | |
705 | * by the change. | |
706 | * | |
707 | * Results: | |
708 | * None. | |
709 | * | |
710 | * Side effects: | |
711 | * Priorities may be changed for some or all of the tags in | |
712 | * textPtr. The tags will be arranged so that there is exactly | |
713 | * one tag at each priority level between 0 and textPtr->numTags-1, | |
714 | * with tagPtr at priority "prio". | |
715 | * | |
716 | *---------------------------------------------------------------------- | |
717 | */ | |
718 | ||
719 | static void | |
720 | ChangeTagPriority(textPtr, tagPtr, prio) | |
721 | TkText *textPtr; /* Information about text widget. */ | |
722 | TkTextTag *tagPtr; /* Tag whose priority is to be | |
723 | * changed. */ | |
724 | int prio; /* New priority for tag. */ | |
725 | { | |
726 | int low, high, delta; | |
727 | register TkTextTag *tagPtr2; | |
728 | Tcl_HashEntry *hPtr; | |
729 | Tcl_HashSearch search; | |
730 | ||
731 | if (prio < 0) { | |
732 | prio = 0; | |
733 | } | |
734 | if (prio >= textPtr->numTags) { | |
735 | prio = textPtr->numTags-1; | |
736 | } | |
737 | if (prio == tagPtr->priority) { | |
738 | return; | |
739 | } else if (prio < tagPtr->priority) { | |
740 | low = prio; | |
741 | high = tagPtr->priority-1; | |
742 | delta = 1; | |
743 | } else { | |
744 | low = tagPtr->priority+1; | |
745 | high = prio; | |
746 | delta = -1; | |
747 | } | |
748 | for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); | |
749 | hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { | |
750 | tagPtr2 = (TkTextTag *) Tcl_GetHashValue(hPtr); | |
751 | if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) { | |
752 | tagPtr2->priority += delta; | |
753 | } | |
754 | } | |
755 | tagPtr->priority = prio; | |
756 | } | |
757 | \f | |
758 | /* | |
759 | *-------------------------------------------------------------- | |
760 | * | |
761 | * TkTextBindProc -- | |
762 | * | |
763 | * This procedure is invoked by the Tk dispatcher to handle | |
764 | * events associated with bindings on items. | |
765 | * | |
766 | * Results: | |
767 | * None. | |
768 | * | |
769 | * Side effects: | |
770 | * Depends on the command invoked as part of the binding | |
771 | * (if there was any). | |
772 | * | |
773 | *-------------------------------------------------------------- | |
774 | */ | |
775 | ||
776 | void | |
777 | TkTextBindProc(clientData, eventPtr) | |
778 | ClientData clientData; /* Pointer to canvas structure. */ | |
779 | XEvent *eventPtr; /* Pointer to X event that just | |
780 | * happened. */ | |
781 | { | |
782 | TkText *textPtr = (TkText *) clientData; | |
783 | int repick = 0; | |
784 | ||
785 | Tk_Preserve((ClientData) textPtr); | |
786 | ||
787 | /* | |
788 | * This code simulates grabs for mouse buttons by refusing to | |
789 | * pick a new current character between the time a mouse button goes | |
790 | * down and the time when the last mouse button is released. | |
791 | */ | |
792 | ||
793 | if (eventPtr->type == ButtonPress) { | |
794 | textPtr->flags |= BUTTON_DOWN; | |
795 | } else if (eventPtr->type == ButtonRelease) { | |
796 | int mask; | |
797 | ||
798 | switch (eventPtr->xbutton.button) { | |
799 | case Button1: | |
800 | mask = Button1Mask; | |
801 | break; | |
802 | case Button2: | |
803 | mask = Button2Mask; | |
804 | break; | |
805 | case Button3: | |
806 | mask = Button3Mask; | |
807 | break; | |
808 | case Button4: | |
809 | mask = Button4Mask; | |
810 | break; | |
811 | case Button5: | |
812 | mask = Button5Mask; | |
813 | break; | |
814 | default: | |
815 | mask = 0; | |
816 | break; | |
817 | } | |
818 | if ((eventPtr->xbutton.state & (Button1Mask|Button2Mask | |
819 | |Button3Mask|Button4Mask|Button5Mask)) == mask) { | |
820 | textPtr->flags &= ~BUTTON_DOWN; | |
821 | repick = 1; | |
822 | } | |
823 | } else if ((eventPtr->type == EnterNotify) | |
824 | || (eventPtr->type == LeaveNotify)) { | |
825 | TkTextPickCurrent(textPtr, eventPtr); | |
826 | goto done; | |
827 | } else if (eventPtr->type == MotionNotify) { | |
828 | TkTextPickCurrent(textPtr, eventPtr); | |
829 | } | |
830 | TextDoEvent(textPtr, eventPtr); | |
831 | if (repick) { | |
832 | unsigned int oldState; | |
833 | ||
834 | oldState = eventPtr->xbutton.state; | |
835 | eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask | |
836 | |Button3Mask|Button4Mask|Button5Mask); | |
837 | TkTextPickCurrent(textPtr, eventPtr); | |
838 | eventPtr->xbutton.state = oldState; | |
839 | } | |
840 | ||
841 | done: | |
842 | Tk_Release((ClientData) textPtr); | |
843 | } | |
844 | \f | |
845 | /* | |
846 | *-------------------------------------------------------------- | |
847 | * | |
848 | * TkTextPickCurrent -- | |
849 | * | |
850 | * Find the topmost item in a canvas that contains a given | |
851 | * location and mark the the current item. If the current | |
852 | * item has changed, generate a fake exit event on the old | |
853 | * current item and a fake enter event on the new current | |
854 | * item. | |
855 | * | |
856 | * Results: | |
857 | * None. | |
858 | * | |
859 | * Side effects: | |
860 | * The current item for textPtr may change. If it does, | |
861 | * then the commands associated with item entry and exit | |
862 | * could do just about anything. | |
863 | * | |
864 | *-------------------------------------------------------------- | |
865 | */ | |
866 | ||
867 | void | |
868 | TkTextPickCurrent(textPtr, eventPtr) | |
869 | register TkText *textPtr; /* Text widget in which to select | |
870 | * current character. */ | |
871 | XEvent *eventPtr; /* Event describing location of | |
872 | * mouse cursor. Must be EnterWindow, | |
873 | * LeaveWindow, ButtonRelease, or | |
874 | * MotionNotify. */ | |
875 | { | |
876 | TkTextLine *linePtr; | |
877 | int ch; | |
878 | ||
879 | /* | |
880 | * If a button is down, then don't do anything at all; we'll be | |
881 | * called again when all buttons are up, and we can repick then. | |
882 | * This implements a form of mouse grabbing. | |
883 | */ | |
884 | ||
885 | if (textPtr->flags & BUTTON_DOWN) { | |
886 | return; | |
887 | } | |
888 | ||
889 | /* | |
890 | * Save information about this event in the widget for use if we have | |
891 | * to synthesize more enter and leave events later (e.g. because a | |
892 | * character was deleting, causing a new character to be underneath | |
893 | * the mouse cursor). Also translate MotionNotify events into | |
894 | * EnterNotify events, since that's what gets reported to event | |
895 | * handlers when the current character changes. | |
896 | */ | |
897 | ||
898 | if (eventPtr != &textPtr->pickEvent) { | |
899 | if ((eventPtr->type == MotionNotify) | |
900 | || (eventPtr->type == ButtonRelease)) { | |
901 | textPtr->pickEvent.xcrossing.type = EnterNotify; | |
902 | textPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; | |
903 | textPtr->pickEvent.xcrossing.send_event | |
904 | = eventPtr->xmotion.send_event; | |
905 | textPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; | |
906 | textPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; | |
907 | textPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; | |
908 | textPtr->pickEvent.xcrossing.subwindow = None; | |
909 | textPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; | |
910 | textPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; | |
911 | textPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; | |
912 | textPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; | |
913 | textPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; | |
914 | textPtr->pickEvent.xcrossing.mode = NotifyNormal; | |
915 | textPtr->pickEvent.xcrossing.detail = NotifyNonlinear; | |
916 | textPtr->pickEvent.xcrossing.same_screen | |
917 | = eventPtr->xmotion.same_screen; | |
918 | textPtr->pickEvent.xcrossing.focus = False; | |
919 | textPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; | |
920 | } else { | |
921 | textPtr->pickEvent = *eventPtr; | |
922 | } | |
923 | } | |
924 | ||
925 | linePtr = NULL; | |
926 | if (textPtr->pickEvent.type != LeaveNotify) { | |
927 | linePtr = TkTextCharAtLoc(textPtr, textPtr->pickEvent.xcrossing.x, | |
928 | textPtr->pickEvent.xcrossing.y, &ch); | |
929 | } | |
930 | ||
931 | /* | |
932 | * Simulate a LeaveNotify event on the previous current character and | |
933 | * an EnterNotify event on the new current character. Also, move the | |
934 | * "current" mark to its new place. | |
935 | */ | |
936 | ||
937 | if (textPtr->flags & IN_CURRENT) { | |
938 | if ((linePtr == textPtr->currentAnnotPtr->linePtr) | |
939 | && (ch == textPtr->currentAnnotPtr->ch)) { | |
940 | return; | |
941 | } | |
942 | } else { | |
943 | if (linePtr == NULL) { | |
944 | return; | |
945 | } | |
946 | } | |
947 | if (textPtr->flags & IN_CURRENT) { | |
948 | XEvent event; | |
949 | ||
950 | event = textPtr->pickEvent; | |
951 | event.type = LeaveNotify; | |
952 | TextDoEvent(textPtr, &event); | |
953 | textPtr->flags &= ~IN_CURRENT; | |
954 | } | |
955 | if (linePtr != NULL) { | |
956 | XEvent event; | |
957 | ||
958 | TkBTreeRemoveAnnotation(textPtr->currentAnnotPtr); | |
959 | textPtr->currentAnnotPtr->linePtr = linePtr; | |
960 | textPtr->currentAnnotPtr->ch = ch; | |
961 | TkBTreeAddAnnotation(textPtr->currentAnnotPtr); | |
962 | event = textPtr->pickEvent; | |
963 | event.type = EnterNotify; | |
964 | TextDoEvent(textPtr, &event); | |
965 | textPtr->flags |= IN_CURRENT; | |
966 | } | |
967 | } | |
968 | \f | |
969 | /* | |
970 | *---------------------------------------------------------------------- | |
971 | * | |
972 | * TkTextUnpickCurrent -- | |
973 | * | |
974 | * This procedure is called when the "current" character is | |
975 | * deleted: it synthesizes a "leave" event for the character. | |
976 | * | |
977 | * Results: | |
978 | * None. | |
979 | * | |
980 | * Side effects: | |
981 | * A binding associated with one of the tags on the current | |
982 | * character may be triggered. | |
983 | * | |
984 | *---------------------------------------------------------------------- | |
985 | */ | |
986 | ||
987 | void | |
988 | TkTextUnpickCurrent(textPtr) | |
989 | TkText *textPtr; /* Text widget information. */ | |
990 | { | |
991 | if (textPtr->flags & IN_CURRENT) { | |
992 | XEvent event; | |
993 | ||
994 | event = textPtr->pickEvent; | |
995 | event.type = LeaveNotify; | |
996 | TextDoEvent(textPtr, &event); | |
997 | textPtr->flags &= ~IN_CURRENT; | |
998 | } | |
999 | } | |
1000 | \f | |
1001 | /* | |
1002 | *-------------------------------------------------------------- | |
1003 | * | |
1004 | * TextDoEvent -- | |
1005 | * | |
1006 | * This procedure is called to invoke binding processing | |
1007 | * for a new event that is associated with the current character | |
1008 | * for a text widget. | |
1009 | * | |
1010 | * Results: | |
1011 | * None. | |
1012 | * | |
1013 | * Side effects: | |
1014 | * Depends on the bindings for the text. | |
1015 | * | |
1016 | *-------------------------------------------------------------- | |
1017 | */ | |
1018 | ||
1019 | static void | |
1020 | TextDoEvent(textPtr, eventPtr) | |
1021 | TkText *textPtr; /* Text widget in which event | |
1022 | * occurred. */ | |
1023 | XEvent *eventPtr; /* Real or simulated X event that | |
1024 | * is to be processed. */ | |
1025 | { | |
1026 | TkTextTag **tagArrayPtr, **p1, **p2, *tmp; | |
1027 | int numTags; | |
1028 | ||
1029 | if (textPtr->bindingTable == NULL) { | |
1030 | return; | |
1031 | } | |
1032 | ||
1033 | /* | |
1034 | * Set up an array containing all of the tags that are associated | |
1035 | * with the current character. This array will be used to look | |
1036 | * for bindings. If there are no tags then there can't be any | |
1037 | * bindings. | |
1038 | */ | |
1039 | ||
1040 | tagArrayPtr = TkBTreeGetTags(textPtr->tree, | |
1041 | textPtr->currentAnnotPtr->linePtr, textPtr->currentAnnotPtr->ch, | |
1042 | &numTags); | |
1043 | if (numTags == 0) { | |
1044 | return; | |
1045 | } | |
1046 | ||
1047 | /* | |
1048 | * Sort the array of tags. SortTags sorts it backwards, so after it | |
1049 | * returns we have to reverse the order in the array. | |
1050 | */ | |
1051 | ||
1052 | SortTags(numTags, tagArrayPtr); | |
1053 | for (p1 = tagArrayPtr, p2 = tagArrayPtr + numTags - 1; | |
1054 | p1 < p2; p1++, p2--) { | |
1055 | tmp = *p1; | |
1056 | *p1 = *p2; | |
1057 | *p2 = tmp; | |
1058 | } | |
1059 | ||
1060 | /* | |
1061 | * Invoke the binding system, then free up the tag array. | |
1062 | */ | |
1063 | ||
1064 | Tk_BindEvent(textPtr->bindingTable, eventPtr, textPtr->tkwin, | |
1065 | numTags, (ClientData *) tagArrayPtr); | |
1066 | ckfree((char *) tagArrayPtr); | |
1067 | } |