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