1/* 2 * tkTextMark.c -- 3 * 4 * This file contains the procedure that implement marks for 5 * text widgets. 6 * 7 * Copyright (c) 1994 The Regents of the University of California. 8 * Copyright (c) 1994-1997 Sun Microsystems, Inc. 9 * 10 * See the file "license.terms" for information on usage and redistribution 11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * RCS: @(#) $Id: tkTextMark.c,v 1.6 2002/08/05 04:30:40 dgp Exp $ 14 */ 15 16#include "tkInt.h" 17#include "tkText.h" 18#include "tkPort.h" 19 20/* 21 * Macro that determines the size of a mark segment: 22 */ 23 24#define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \ 25 + sizeof(TkTextMark))) 26 27/* 28 * Forward references for procedures defined in this file: 29 */ 30 31static void InsertUndisplayProc _ANSI_ARGS_((TkText *textPtr, 32 TkTextDispChunk *chunkPtr)); 33static int MarkDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, 34 TkTextLine *linePtr, int treeGone)); 35static TkTextSegment * MarkCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, 36 TkTextLine *linePtr)); 37static void MarkCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, 38 TkTextLine *linePtr)); 39static int MarkLayoutProc _ANSI_ARGS_((TkText *textPtr, 40 TkTextIndex *indexPtr, TkTextSegment *segPtr, 41 int offset, int maxX, int maxChars, 42 int noCharsYet, TkWrapMode wrapMode, 43 TkTextDispChunk *chunkPtr)); 44static int MarkFindNext _ANSI_ARGS_((Tcl_Interp *interp, 45 TkText *textPtr, CONST char *markName)); 46static int MarkFindPrev _ANSI_ARGS_((Tcl_Interp *interp, 47 TkText *textPtr, CONST char *markName)); 48 49 50/* 51 * The following structures declare the "mark" segment types. 52 * There are actually two types for marks, one with left gravity 53 * and one with right gravity. They are identical except for 54 * their gravity property. 55 */ 56 57Tk_SegType tkTextRightMarkType = { 58 "mark", /* name */ 59 0, /* leftGravity */ 60 (Tk_SegSplitProc *) NULL, /* splitProc */ 61 MarkDeleteProc, /* deleteProc */ 62 MarkCleanupProc, /* cleanupProc */ 63 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */ 64 MarkLayoutProc, /* layoutProc */ 65 MarkCheckProc /* checkProc */ 66}; 67 68Tk_SegType tkTextLeftMarkType = { 69 "mark", /* name */ 70 1, /* leftGravity */ 71 (Tk_SegSplitProc *) NULL, /* splitProc */ 72 MarkDeleteProc, /* deleteProc */ 73 MarkCleanupProc, /* cleanupProc */ 74 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */ 75 MarkLayoutProc, /* layoutProc */ 76 MarkCheckProc /* checkProc */ 77}; 78 79/* 80 *-------------------------------------------------------------- 81 * 82 * TkTextMarkCmd -- 83 * 84 * This procedure is invoked to process the "mark" options of 85 * the widget command for text widgets. See the user documentation 86 * for details on what it does. 87 * 88 * Results: 89 * A standard Tcl result. 90 * 91 * Side effects: 92 * See the user documentation. 93 * 94 *-------------------------------------------------------------- 95 */ 96 97int 98TkTextMarkCmd(textPtr, interp, argc, argv) 99 register TkText *textPtr; /* Information about text widget. */ 100 Tcl_Interp *interp; /* Current interpreter. */ 101 int argc; /* Number of arguments. */ 102 CONST char **argv; /* Argument strings. Someone else has already 103 * parsed this command enough to know that 104 * argv[1] is "mark". */ 105{ 106 int c, i; 107 size_t length; 108 Tcl_HashEntry *hPtr; 109 TkTextSegment *markPtr; 110 Tcl_HashSearch search; 111 TkTextIndex index; 112 Tk_SegType *newTypePtr; 113 114 if (argc < 3) { 115 Tcl_AppendResult(interp, "wrong # args: should be \"", 116 argv[0], " mark option ?arg arg ...?\"", (char *) NULL); 117 return TCL_ERROR; 118 } 119 c = argv[2][0]; 120 length = strlen(argv[2]); 121 if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) { 122 if (argc < 4 || argc > 5) { 123 Tcl_AppendResult(interp, "wrong # args: should be \"", 124 argv[0], " mark gravity markName ?gravity?\"", 125 (char *) NULL); 126 return TCL_ERROR; 127 } 128 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]); 129 if (hPtr == NULL) { 130 Tcl_AppendResult(interp, "there is no mark named \"", 131 argv[3], "\"", (char *) NULL); 132 return TCL_ERROR; 133 } 134 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); 135 if (argc == 4) { 136 if (markPtr->typePtr == &tkTextRightMarkType) { 137 Tcl_SetResult(interp, "right", TCL_STATIC); 138 } else { 139 Tcl_SetResult(interp, "left", TCL_STATIC); 140 } 141 return TCL_OK; 142 } 143 length = strlen(argv[4]); 144 c = argv[4][0]; 145 if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) { 146 newTypePtr = &tkTextLeftMarkType; 147 } else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) { 148 newTypePtr = &tkTextRightMarkType; 149 } else { 150 Tcl_AppendResult(interp, "bad mark gravity \"", 151 argv[4], "\": must be left or right", (char *) NULL); 152 return TCL_ERROR; 153 } 154 TkTextMarkSegToIndex(textPtr, markPtr, &index); 155 TkBTreeUnlinkSegment(textPtr->tree, markPtr, 156 markPtr->body.mark.linePtr); 157 markPtr->typePtr = newTypePtr; 158 TkBTreeLinkSegment(markPtr, &index); 159 } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) { 160 if (argc != 3) { 161 Tcl_AppendResult(interp, "wrong # args: should be \"", 162 argv[0], " mark names\"", (char *) NULL); 163 return TCL_ERROR; 164 } 165 for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search); 166 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { 167 Tcl_AppendElement(interp, 168 Tcl_GetHashKey(&textPtr->markTable, hPtr)); 169 } 170 } else if ((c == 'n') && (strncmp(argv[2], "next", length) == 0)) { 171 if (argc != 4) { 172 Tcl_AppendResult(interp, "wrong # args: should be \"", 173 argv[0], " mark next index\"", (char *) NULL); 174 return TCL_ERROR; 175 } 176 return MarkFindNext(interp, textPtr, argv[3]); 177 } else if ((c == 'p') && (strncmp(argv[2], "previous", length) == 0)) { 178 if (argc != 4) { 179 Tcl_AppendResult(interp, "wrong # args: should be \"", 180 argv[0], " mark previous index\"", (char *) NULL); 181 return TCL_ERROR; 182 } 183 return MarkFindPrev(interp, textPtr, argv[3]); 184 } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) { 185 if (argc != 5) { 186 Tcl_AppendResult(interp, "wrong # args: should be \"", 187 argv[0], " mark set markName index\"", (char *) NULL); 188 return TCL_ERROR; 189 } 190 if (TkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) { 191 return TCL_ERROR; 192 } 193 TkTextSetMark(textPtr, argv[3], &index); 194 } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) { 195 for (i = 3; i < argc; i++) { 196 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]); 197 if (hPtr != NULL) { 198 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); 199 if ((markPtr == textPtr->insertMarkPtr) 200 || (markPtr == textPtr->currentMarkPtr)) { 201 continue; 202 } 203 TkBTreeUnlinkSegment(textPtr->tree, markPtr, 204 markPtr->body.mark.linePtr); 205 Tcl_DeleteHashEntry(hPtr); 206 ckfree((char *) markPtr); 207 } 208 } 209 } else { 210 Tcl_AppendResult(interp, "bad mark option \"", argv[2], 211 "\": must be gravity, names, next, previous, set, or unset", 212 (char *) NULL); 213 return TCL_ERROR; 214 } 215 return TCL_OK; 216} 217 218/* 219 *---------------------------------------------------------------------- 220 * 221 * TkTextSetMark -- 222 * 223 * Set a mark to a particular position, creating a new mark if 224 * one doesn't already exist. 225 * 226 * Results: 227 * The return value is a pointer to the mark that was just set. 228 * 229 * Side effects: 230 * A new mark is created, or an existing mark is moved. 231 * 232 *---------------------------------------------------------------------- 233 */ 234 235TkTextSegment * 236TkTextSetMark(textPtr, name, indexPtr) 237 TkText *textPtr; /* Text widget in which to create mark. */ 238 CONST char *name; /* Name of mark to set. */ 239 TkTextIndex *indexPtr; /* Where to set mark. */ 240{ 241 Tcl_HashEntry *hPtr; 242 TkTextSegment *markPtr; 243 TkTextIndex insertIndex; 244 int new; 245 246 hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new); 247 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); 248 if (!new) { 249 /* 250 * If this is the insertion point that's being moved, be sure 251 * to force a display update at the old position. Also, don't 252 * let the insertion cursor be after the final newline of the 253 * file. 254 */ 255 256 if (markPtr == textPtr->insertMarkPtr) { 257 TkTextIndex index, index2; 258 TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); 259 TkTextIndexForwChars(&index, 1, &index2); 260 TkTextChanged(textPtr, &index, &index2); 261 if (TkBTreeLineIndex(indexPtr->linePtr) 262 == TkBTreeNumLines(textPtr->tree)) { 263 TkTextIndexBackChars(indexPtr, 1, &insertIndex); 264 indexPtr = &insertIndex; 265 } 266 } 267 TkBTreeUnlinkSegment(textPtr->tree, markPtr, 268 markPtr->body.mark.linePtr); 269 } else { 270 markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE); 271 markPtr->typePtr = &tkTextRightMarkType; 272 markPtr->size = 0; 273 markPtr->body.mark.textPtr = textPtr; 274 markPtr->body.mark.linePtr = indexPtr->linePtr; 275 markPtr->body.mark.hPtr = hPtr; 276 Tcl_SetHashValue(hPtr, markPtr); 277 } 278 TkBTreeLinkSegment(markPtr, indexPtr); 279 280 /* 281 * If the mark is the insertion cursor, then update the screen at the 282 * mark's new location. 283 */ 284 285 if (markPtr == textPtr->insertMarkPtr) { 286 TkTextIndex index2; 287 288 TkTextIndexForwChars(indexPtr, 1, &index2); 289 TkTextChanged(textPtr, indexPtr, &index2); 290 } 291 return markPtr; 292} 293 294/* 295 *-------------------------------------------------------------- 296 * 297 * TkTextMarkSegToIndex -- 298 * 299 * Given a segment that is a mark, create an index that 300 * refers to the next text character (or other text segment 301 * with non-zero size) after the mark. 302 * 303 * Results: 304 * *IndexPtr is filled in with index information. 305 * 306 * Side effects: 307 * None. 308 * 309 *-------------------------------------------------------------- 310 */ 311 312void 313TkTextMarkSegToIndex(textPtr, markPtr, indexPtr) 314 TkText *textPtr; /* Text widget containing mark. */ 315 TkTextSegment *markPtr; /* Mark segment. */ 316 TkTextIndex *indexPtr; /* Index information gets stored here. */ 317{ 318 TkTextSegment *segPtr; 319 320 indexPtr->tree = textPtr->tree; 321 indexPtr->linePtr = markPtr->body.mark.linePtr; 322 indexPtr->byteIndex = 0; 323 for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr; 324 segPtr = segPtr->nextPtr) { 325 indexPtr->byteIndex += segPtr->size; 326 } 327} 328 329/* 330 *-------------------------------------------------------------- 331 * 332 * TkTextMarkNameToIndex -- 333 * 334 * Given the name of a mark, return an index corresponding 335 * to the mark name. 336 * 337 * Results: 338 * The return value is TCL_OK if "name" exists as a mark in 339 * the text widget. In this case *indexPtr is filled in with 340 * the next segment whose after the mark whose size is 341 * non-zero. TCL_ERROR is returned if the mark doesn't exist 342 * in the text widget. 343 * 344 * Side effects: 345 * None. 346 * 347 *-------------------------------------------------------------- 348 */ 349 350int 351TkTextMarkNameToIndex(textPtr, name, indexPtr) 352 TkText *textPtr; /* Text widget containing mark. */ 353 CONST char *name; /* Name of mark. */ 354 TkTextIndex *indexPtr; /* Index information gets stored here. */ 355{ 356 Tcl_HashEntry *hPtr; 357 358 hPtr = Tcl_FindHashEntry(&textPtr->markTable, name); 359 if (hPtr == NULL) { 360 return TCL_ERROR; 361 } 362 TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr), 363 indexPtr); 364 return TCL_OK; 365} 366 367/* 368 *-------------------------------------------------------------- 369 * 370 * MarkDeleteProc -- 371 * 372 * This procedure is invoked by the text B-tree code whenever 373 * a mark lies in a range of characters being deleted. 374 * 375 * Results: 376 * Returns 1 to indicate that deletion has been rejected. 377 * 378 * Side effects: 379 * None (even if the whole tree is being deleted we don't 380 * free up the mark; it will be done elsewhere). 381 * 382 *-------------------------------------------------------------- 383 */ 384 385 /* ARGSUSED */ 386static int 387MarkDeleteProc(segPtr, linePtr, treeGone) 388 TkTextSegment *segPtr; /* Segment being deleted. */ 389 TkTextLine *linePtr; /* Line containing segment. */ 390 int treeGone; /* Non-zero means the entire tree is 391 * being deleted, so everything must 392 * get cleaned up. */ 393{ 394 return 1; 395} 396 397/* 398 *-------------------------------------------------------------- 399 * 400 * MarkCleanupProc -- 401 * 402 * This procedure is invoked by the B-tree code whenever a 403 * mark segment is moved from one line to another. 404 * 405 * Results: 406 * None. 407 * 408 * Side effects: 409 * The linePtr field of the segment gets updated. 410 * 411 *-------------------------------------------------------------- 412 */ 413 414static TkTextSegment * 415MarkCleanupProc(markPtr, linePtr) 416 TkTextSegment *markPtr; /* Mark segment that's being moved. */ 417 TkTextLine *linePtr; /* Line that now contains segment. */ 418{ 419 markPtr->body.mark.linePtr = linePtr; 420 return markPtr; 421} 422 423/* 424 *-------------------------------------------------------------- 425 * 426 * MarkLayoutProc -- 427 * 428 * This procedure is the "layoutProc" for mark segments. 429 * 430 * Results: 431 * If the mark isn't the insertion cursor then the return 432 * value is -1 to indicate that this segment shouldn't be 433 * displayed. If the mark is the insertion character then 434 * 1 is returned and the chunkPtr structure is filled in. 435 * 436 * Side effects: 437 * None, except for filling in chunkPtr. 438 * 439 *-------------------------------------------------------------- 440 */ 441 442 /*ARGSUSED*/ 443static int 444MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars, 445 noCharsYet, wrapMode, chunkPtr) 446 TkText *textPtr; /* Text widget being layed out. */ 447 TkTextIndex *indexPtr; /* Identifies first character in chunk. */ 448 TkTextSegment *segPtr; /* Segment corresponding to indexPtr. */ 449 int offset; /* Offset within segPtr corresponding to 450 * indexPtr (always 0). */ 451 int maxX; /* Chunk must not occupy pixels at this 452 * position or higher. */ 453 int maxChars; /* Chunk must not include more than this 454 * many characters. */ 455 int noCharsYet; /* Non-zero means no characters have been 456 * assigned to this line yet. */ 457 TkWrapMode wrapMode; /* Not used. */ 458 register TkTextDispChunk *chunkPtr; 459 /* Structure to fill in with information 460 * about this chunk. The x field has already 461 * been set by the caller. */ 462{ 463 if (segPtr != textPtr->insertMarkPtr) { 464 return -1; 465 } 466 467 chunkPtr->displayProc = TkTextInsertDisplayProc; 468 chunkPtr->undisplayProc = InsertUndisplayProc; 469 chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL; 470 chunkPtr->bboxProc = (Tk_ChunkBboxProc *) NULL; 471 chunkPtr->numBytes = 0; 472 chunkPtr->minAscent = 0; 473 chunkPtr->minDescent = 0; 474 chunkPtr->minHeight = 0; 475 chunkPtr->width = 0; 476 477 /* 478 * Note: can't break a line after the insertion cursor: this 479 * prevents the insertion cursor from being stranded at the end 480 * of a line. 481 */ 482 483 chunkPtr->breakIndex = -1; 484 chunkPtr->clientData = (ClientData) textPtr; 485 return 1; 486} 487 488/* 489 *-------------------------------------------------------------- 490 * 491 * TkTextInsertDisplayProc -- 492 * 493 * This procedure is called to display the insertion 494 * cursor. 495 * 496 * Results: 497 * None. 498 * 499 * Side effects: 500 * Graphics are drawn. 501 * 502 *-------------------------------------------------------------- 503 */ 504 505 /* ARGSUSED */ 506void 507TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) 508 TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ 509 int x; /* X-position in dst at which to 510 * draw this chunk (may differ from 511 * the x-position in the chunk because 512 * of scrolling). */ 513 int y; /* Y-position at which to draw this 514 * chunk in dst (x-position is in 515 * the chunk itself). */ 516 int height; /* Total height of line. */ 517 int baseline; /* Offset of baseline from y. */ 518 Display *display; /* Display to use for drawing. */ 519 Drawable dst; /* Pixmap or window in which to draw 520 * chunk. */ 521 int screenY; /* Y-coordinate in text window that 522 * corresponds to y. */ 523{ 524 TkText *textPtr = (TkText *) chunkPtr->clientData; 525 int halfWidth = textPtr->insertWidth/2; 526 527 if ((x + halfWidth) < 0) { 528 /* 529 * The insertion cursor is off-screen. 530 * Indicate caret at 0,0 and return. 531 */ 532 533 Tk_SetCaretPos(textPtr->tkwin, 0, 0, height); 534 return; 535 } 536 537 Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height); 538 539 /* 540 * As a special hack to keep the cursor visible on mono displays 541 * (or anywhere else that the selection and insertion cursors 542 * have the same color) write the default background in the cursor 543 * area (instead of nothing) when the cursor isn't on. Otherwise 544 * the selection might hide the cursor. 545 */ 546 547 if (textPtr->flags & INSERT_ON) { 548 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, 549 x - halfWidth, y, textPtr->insertWidth, height, 550 textPtr->insertBorderWidth, TK_RELIEF_RAISED); 551 } else if (textPtr->selBorder == textPtr->insertBorder) { 552 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border, 553 x - halfWidth, y, textPtr->insertWidth, height, 554 0, TK_RELIEF_FLAT); 555 } 556} 557 558/* 559 *-------------------------------------------------------------- 560 * 561 * InsertUndisplayProc -- 562 * 563 * This procedure is called when the insertion cursor is no 564 * longer at a visible point on the display. It does nothing 565 * right now. 566 * 567 * Results: 568 * None. 569 * 570 * Side effects: 571 * None. 572 * 573 *-------------------------------------------------------------- 574 */ 575 576 /* ARGSUSED */ 577static void 578InsertUndisplayProc(textPtr, chunkPtr) 579 TkText *textPtr; /* Overall information about text 580 * widget. */ 581 TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ 582{ 583 return; 584} 585 586/* 587 *-------------------------------------------------------------- 588 * 589 * MarkCheckProc -- 590 * 591 * This procedure is invoked by the B-tree code to perform 592 * consistency checks on mark segments. 593 * 594 * Results: 595 * None. 596 * 597 * Side effects: 598 * The procedure panics if it detects anything wrong with 599 * the mark. 600 * 601 *-------------------------------------------------------------- 602 */ 603 604static void 605MarkCheckProc(markPtr, linePtr) 606 TkTextSegment *markPtr; /* Segment to check. */ 607 TkTextLine *linePtr; /* Line containing segment. */ 608{ 609 Tcl_HashSearch search; 610 Tcl_HashEntry *hPtr; 611 612 if (markPtr->body.mark.linePtr != linePtr) { 613 panic("MarkCheckProc: markPtr->body.mark.linePtr bogus"); 614 } 615 616 /* 617 * Make sure that the mark is still present in the text's mark 618 * hash table. 619 */ 620 621 for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable, 622 &search); hPtr != markPtr->body.mark.hPtr; 623 hPtr = Tcl_NextHashEntry(&search)) { 624 if (hPtr == NULL) { 625 panic("MarkCheckProc couldn't find hash table entry for mark"); 626 } 627 } 628} 629 630/* 631 *-------------------------------------------------------------- 632 * 633 * MarkFindNext -- 634 * 635 * This procedure searches forward for the next mark. 636 * 637 * Results: 638 * A standard Tcl result, which is a mark name or an empty string. 639 * 640 * Side effects: 641 * None. 642 * 643 *-------------------------------------------------------------- 644 */ 645 646static int 647MarkFindNext(interp, textPtr, string) 648 Tcl_Interp *interp; /* For error reporting */ 649 TkText *textPtr; /* The widget */ 650 CONST char *string; /* The starting index or mark name */ 651{ 652 TkTextIndex index; 653 Tcl_HashEntry *hPtr; 654 register TkTextSegment *segPtr; 655 int offset; 656 657 658 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); 659 if (hPtr != NULL) { 660 /* 661 * If given a mark name, return the next mark in the list of 662 * segments, even if it happens to be at the same character position. 663 */ 664 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); 665 TkTextMarkSegToIndex(textPtr, segPtr, &index); 666 segPtr = segPtr->nextPtr; 667 } else { 668 /* 669 * For non-mark name indices we want to return any marks that 670 * are right at the index. 671 */ 672 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { 673 return TCL_ERROR; 674 } 675 for (offset = 0, segPtr = index.linePtr->segPtr; 676 segPtr != NULL && offset < index.byteIndex; 677 offset += segPtr->size, segPtr = segPtr->nextPtr) { 678 /* Empty loop body */ ; 679 } 680 } 681 while (1) { 682 /* 683 * segPtr points at the first possible candidate, 684 * or NULL if we ran off the end of the line. 685 */ 686 for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) { 687 if (segPtr->typePtr == &tkTextRightMarkType || 688 segPtr->typePtr == &tkTextLeftMarkType) { 689 Tcl_SetResult(interp, 690 Tcl_GetHashKey(&textPtr->markTable, segPtr->body.mark.hPtr), 691 TCL_STATIC); 692 return TCL_OK; 693 } 694 } 695 index.linePtr = TkBTreeNextLine(index.linePtr); 696 if (index.linePtr == (TkTextLine *) NULL) { 697 return TCL_OK; 698 } 699 index.byteIndex = 0; 700 segPtr = index.linePtr->segPtr; 701 } 702} 703 704/* 705 *-------------------------------------------------------------- 706 * 707 * MarkFindPrev -- 708 * 709 * This procedure searches backwards for the previous mark. 710 * 711 * Results: 712 * A standard Tcl result, which is a mark name or an empty string. 713 * 714 * Side effects: 715 * None. 716 * 717 *-------------------------------------------------------------- 718 */ 719 720static int 721MarkFindPrev(interp, textPtr, string) 722 Tcl_Interp *interp; /* For error reporting */ 723 TkText *textPtr; /* The widget */ 724 CONST char *string; /* The starting index or mark name */ 725{ 726 TkTextIndex index; 727 Tcl_HashEntry *hPtr; 728 register TkTextSegment *segPtr, *seg2Ptr, *prevPtr; 729 int offset; 730 731 732 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); 733 if (hPtr != NULL) { 734 /* 735 * If given a mark name, return the previous mark in the list of 736 * segments, even if it happens to be at the same character position. 737 */ 738 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); 739 TkTextMarkSegToIndex(textPtr, segPtr, &index); 740 } else { 741 /* 742 * For non-mark name indices we do not return any marks that 743 * are right at the index. 744 */ 745 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { 746 return TCL_ERROR; 747 } 748 for (offset = 0, segPtr = index.linePtr->segPtr; 749 segPtr != NULL && offset < index.byteIndex; 750 offset += segPtr->size, segPtr = segPtr->nextPtr) { 751 /* Empty loop body */ ; 752 } 753 } 754 while (1) { 755 /* 756 * segPtr points just past the first possible candidate, 757 * or at the begining of the line. 758 */ 759 for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr; 760 seg2Ptr != NULL && seg2Ptr != segPtr; 761 seg2Ptr = seg2Ptr->nextPtr) { 762 if (seg2Ptr->typePtr == &tkTextRightMarkType || 763 seg2Ptr->typePtr == &tkTextLeftMarkType) { 764 prevPtr = seg2Ptr; 765 } 766 } 767 if (prevPtr != NULL) { 768 Tcl_SetResult(interp, 769 Tcl_GetHashKey(&textPtr->markTable, prevPtr->body.mark.hPtr), 770 TCL_STATIC); 771 return TCL_OK; 772 } 773 index.linePtr = TkBTreePreviousLine(index.linePtr); 774 if (index.linePtr == (TkTextLine *) NULL) { 775 return TCL_OK; 776 } 777 segPtr = NULL; 778 } 779} 780