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