text.tcl (33087B)
1 # text.tcl -- 2 # 3 # This file defines the default bindings for Tk text widgets and provides 4 # procedures that help in implementing the bindings. 5 # 6 # Copyright (c) 1992-1994 The Regents of the University of California. 7 # Copyright (c) 1994-1997 Sun Microsystems, Inc. 8 # Copyright (c) 1998 by Scriptics Corporation. 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 14 #------------------------------------------------------------------------- 15 # Elements of ::tk::Priv that are used in this file: 16 # 17 # afterId - If non-null, it means that auto-scanning is underway 18 # and it gives the "after" id for the next auto-scan 19 # command to be executed. 20 # char - Character position on the line; kept in order 21 # to allow moving up or down past short lines while 22 # still remembering the desired position. 23 # mouseMoved - Non-zero means the mouse has moved a significant 24 # amount since the button went down (so, for example, 25 # start dragging out a selection). 26 # prevPos - Used when moving up or down lines via the keyboard. 27 # Keeps track of the previous insert position, so 28 # we can distinguish a series of ups and downs, all 29 # in a row, from a new up or down. 30 # selectMode - The style of selection currently underway: 31 # char, word, or line. 32 # x, y - Last known mouse coordinates for scanning 33 # and auto-scanning. 34 # 35 #------------------------------------------------------------------------- 36 37 #------------------------------------------------------------------------- 38 # The code below creates the default class bindings for text widgets. 39 #------------------------------------------------------------------------- 40 41 # Standard Motif bindings: 42 43 bind Text <1> { 44 tk::TextButton1 %W %x %y 45 %W tag remove sel 0.0 end 46 } 47 bind Text <B1-Motion> { 48 set tk::Priv(x) %x 49 set tk::Priv(y) %y 50 tk::TextSelectTo %W %x %y 51 } 52 bind Text <Double-1> { 53 set tk::Priv(selectMode) word 54 tk::TextSelectTo %W %x %y 55 catch {%W mark set insert sel.first} 56 } 57 bind Text <Triple-1> { 58 set tk::Priv(selectMode) line 59 tk::TextSelectTo %W %x %y 60 catch {%W mark set insert sel.first} 61 } 62 bind Text <Shift-1> { 63 tk::TextResetAnchor %W @%x,%y 64 set tk::Priv(selectMode) char 65 tk::TextSelectTo %W %x %y 66 } 67 bind Text <Double-Shift-1> { 68 set tk::Priv(selectMode) word 69 tk::TextSelectTo %W %x %y 1 70 } 71 bind Text <Triple-Shift-1> { 72 set tk::Priv(selectMode) line 73 tk::TextSelectTo %W %x %y 74 } 75 bind Text <B1-Leave> { 76 set tk::Priv(x) %x 77 set tk::Priv(y) %y 78 tk::TextAutoScan %W 79 } 80 bind Text <B1-Enter> { 81 tk::CancelRepeat 82 } 83 bind Text <ButtonRelease-1> { 84 tk::CancelRepeat 85 } 86 bind Text <Control-1> { 87 %W mark set insert @%x,%y 88 # An operation that moves the insert mark without making it 89 # one end of the selection must insert an autoseparator 90 if {[%W cget -autoseparators]} { 91 %W edit separator 92 } 93 } 94 # stop an accidental double click triggering <Double-Button-1> 95 bind Text <Double-Control-1> { # nothing } 96 # stop an accidental movement triggering <B1-Motion> 97 bind Text <Control-B1-Motion> { # nothing } 98 bind Text <<PrevChar>> { 99 tk::TextSetCursor %W insert-1displayindices 100 } 101 bind Text <<NextChar>> { 102 tk::TextSetCursor %W insert+1displayindices 103 } 104 bind Text <<PrevLine>> { 105 tk::TextSetCursor %W [tk::TextUpDownLine %W -1] 106 } 107 bind Text <<NextLine>> { 108 tk::TextSetCursor %W [tk::TextUpDownLine %W 1] 109 } 110 bind Text <<SelectPrevChar>> { 111 tk::TextKeySelect %W [%W index {insert - 1displayindices}] 112 } 113 bind Text <<SelectNextChar>> { 114 tk::TextKeySelect %W [%W index {insert + 1displayindices}] 115 } 116 bind Text <<SelectPrevLine>> { 117 tk::TextKeySelect %W [tk::TextUpDownLine %W -1] 118 } 119 bind Text <<SelectNextLine>> { 120 tk::TextKeySelect %W [tk::TextUpDownLine %W 1] 121 } 122 bind Text <<PrevWord>> { 123 tk::TextSetCursor %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord] 124 } 125 bind Text <<NextWord>> { 126 tk::TextSetCursor %W [tk::TextNextWord %W insert] 127 } 128 bind Text <<PrevPara>> { 129 tk::TextSetCursor %W [tk::TextPrevPara %W insert] 130 } 131 bind Text <<NextPara>> { 132 tk::TextSetCursor %W [tk::TextNextPara %W insert] 133 } 134 bind Text <<SelectPrevWord>> { 135 tk::TextKeySelect %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord] 136 } 137 bind Text <<SelectNextWord>> { 138 tk::TextKeySelect %W [tk::TextNextWord %W insert] 139 } 140 bind Text <<SelectPrevPara>> { 141 tk::TextKeySelect %W [tk::TextPrevPara %W insert] 142 } 143 bind Text <<SelectNextPara>> { 144 tk::TextKeySelect %W [tk::TextNextPara %W insert] 145 } 146 bind Text <Prior> { 147 tk::TextSetCursor %W [tk::TextScrollPages %W -1] 148 } 149 bind Text <Shift-Prior> { 150 tk::TextKeySelect %W [tk::TextScrollPages %W -1] 151 } 152 bind Text <Next> { 153 tk::TextSetCursor %W [tk::TextScrollPages %W 1] 154 } 155 bind Text <Shift-Next> { 156 tk::TextKeySelect %W [tk::TextScrollPages %W 1] 157 } 158 bind Text <Control-Prior> { 159 %W xview scroll -1 page 160 } 161 bind Text <Control-Next> { 162 %W xview scroll 1 page 163 } 164 165 bind Text <<LineStart>> { 166 tk::TextSetCursor %W {insert display linestart} 167 } 168 bind Text <<SelectLineStart>> { 169 tk::TextKeySelect %W {insert display linestart} 170 } 171 bind Text <<LineEnd>> { 172 tk::TextSetCursor %W {insert display lineend} 173 } 174 bind Text <<SelectLineEnd>> { 175 tk::TextKeySelect %W {insert display lineend} 176 } 177 bind Text <Control-Home> { 178 tk::TextSetCursor %W 1.0 179 } 180 bind Text <Control-Shift-Home> { 181 tk::TextKeySelect %W 1.0 182 } 183 bind Text <Control-End> { 184 tk::TextSetCursor %W {end - 1 indices} 185 } 186 bind Text <Control-Shift-End> { 187 tk::TextKeySelect %W {end - 1 indices} 188 } 189 190 bind Text <Tab> { 191 if {[%W cget -state] eq "normal"} { 192 tk::TextInsert %W \t 193 focus %W 194 break 195 } 196 } 197 bind Text <Shift-Tab> { 198 # Needed only to keep <Tab> binding from triggering; doesn't 199 # have to actually do anything. 200 break 201 } 202 bind Text <Control-Tab> { 203 focus [tk_focusNext %W] 204 } 205 bind Text <Control-Shift-Tab> { 206 focus [tk_focusPrev %W] 207 } 208 bind Text <Control-i> { 209 tk::TextInsert %W \t 210 } 211 bind Text <Return> { 212 tk::TextInsert %W \n 213 if {[%W cget -autoseparators]} { 214 %W edit separator 215 } 216 } 217 bind Text <Delete> { 218 if {[tk::TextCursorInSelection %W]} { 219 %W delete sel.first sel.last 220 } else { 221 if {[%W compare end != insert+1c]} { 222 %W delete insert 223 } 224 %W see insert 225 } 226 } 227 bind Text <BackSpace> { 228 if {[tk::TextCursorInSelection %W]} { 229 %W delete sel.first sel.last 230 } else { 231 if {[%W compare insert != 1.0]} { 232 %W delete insert-1c 233 } 234 %W see insert 235 } 236 } 237 238 bind Text <Control-space> { 239 %W mark set [tk::TextAnchor %W] insert 240 } 241 bind Text <Select> { 242 %W mark set [tk::TextAnchor %W] insert 243 } 244 bind Text <Control-Shift-space> { 245 set tk::Priv(selectMode) char 246 tk::TextKeyExtend %W insert 247 } 248 bind Text <Shift-Select> { 249 set tk::Priv(selectMode) char 250 tk::TextKeyExtend %W insert 251 } 252 bind Text <<SelectAll>> { 253 %W tag add sel 1.0 end 254 } 255 bind Text <<SelectNone>> { 256 %W tag remove sel 1.0 end 257 # An operation that clears the selection must insert an autoseparator, 258 # because the selection operation may have moved the insert mark 259 if {[%W cget -autoseparators]} { 260 %W edit separator 261 } 262 } 263 bind Text <<Cut>> { 264 tk_textCut %W 265 } 266 bind Text <<Copy>> { 267 tk_textCopy %W 268 } 269 bind Text <<Paste>> { 270 tk_textPaste %W 271 } 272 bind Text <<Clear>> { 273 # Make <<Clear>> an atomic operation on the Undo stack, 274 # i.e. separate it from other delete operations on either side 275 if {[%W cget -autoseparators]} { 276 %W edit separator 277 } 278 catch {%W delete sel.first sel.last} 279 if {[%W cget -autoseparators]} { 280 %W edit separator 281 } 282 } 283 bind Text <<PasteSelection>> { 284 if {$tk_strictMotif || ![info exists tk::Priv(mouseMoved)] 285 || !$tk::Priv(mouseMoved)} { 286 tk::TextPasteSelection %W %x %y 287 } 288 } 289 bind Text <Insert> { 290 catch {tk::TextInsert %W [::tk::GetSelection %W PRIMARY]} 291 } 292 bind Text <KeyPress> { 293 tk::TextInsert %W %A 294 } 295 296 # Ignore all Alt, Meta, and Control keypresses unless explicitly bound. 297 # Otherwise, if a widget binding for one of these is defined, the 298 # <KeyPress> class binding will also fire and insert the character, 299 # which is wrong. Ditto for <Escape>. 300 301 bind Text <Alt-KeyPress> {# nothing } 302 bind Text <Meta-KeyPress> {# nothing} 303 bind Text <Control-KeyPress> {# nothing} 304 bind Text <Escape> {# nothing} 305 bind Text <KP_Enter> {# nothing} 306 if {[tk windowingsystem] eq "aqua"} { 307 bind Text <Command-KeyPress> {# nothing} 308 } 309 310 # Additional emacs-like bindings: 311 312 bind Text <Control-d> { 313 if {!$tk_strictMotif && [%W compare end != insert+1c]} { 314 %W delete insert 315 } 316 } 317 bind Text <Control-k> { 318 if {!$tk_strictMotif && [%W compare end != insert+1c]} { 319 if {[%W compare insert == {insert lineend}]} { 320 %W delete insert 321 } else { 322 %W delete insert {insert lineend} 323 } 324 } 325 } 326 bind Text <Control-o> { 327 if {!$tk_strictMotif} { 328 %W insert insert \n 329 %W mark set insert insert-1c 330 } 331 } 332 bind Text <Control-t> { 333 if {!$tk_strictMotif} { 334 tk::TextTranspose %W 335 } 336 } 337 338 bind Text <<Undo>> { 339 # An Undo operation may remove the separator at the top of the Undo stack. 340 # Then the item at the top of the stack gets merged with the subsequent changes. 341 # Place separators before and after Undo to prevent this. 342 if {[%W cget -autoseparators]} { 343 %W edit separator 344 } 345 catch { %W edit undo } 346 if {[%W cget -autoseparators]} { 347 %W edit separator 348 } 349 } 350 351 bind Text <<Redo>> { 352 catch { %W edit redo } 353 } 354 355 bind Text <Meta-b> { 356 if {!$tk_strictMotif} { 357 tk::TextSetCursor %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord] 358 } 359 } 360 bind Text <Meta-d> { 361 if {!$tk_strictMotif && [%W compare end != insert+1c]} { 362 %W delete insert [tk::TextNextWord %W insert] 363 } 364 } 365 bind Text <Meta-f> { 366 if {!$tk_strictMotif} { 367 tk::TextSetCursor %W [tk::TextNextWord %W insert] 368 } 369 } 370 bind Text <Meta-less> { 371 if {!$tk_strictMotif} { 372 tk::TextSetCursor %W 1.0 373 } 374 } 375 bind Text <Meta-greater> { 376 if {!$tk_strictMotif} { 377 tk::TextSetCursor %W end-1c 378 } 379 } 380 bind Text <Meta-BackSpace> { 381 if {!$tk_strictMotif} { 382 %W delete [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert 383 } 384 } 385 bind Text <Meta-Delete> { 386 if {!$tk_strictMotif} { 387 %W delete [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert 388 } 389 } 390 391 # Macintosh only bindings: 392 393 if {[tk windowingsystem] eq "aqua"} { 394 bind Text <Control-v> { 395 tk::TextScrollPages %W 1 396 } 397 398 # End of Mac only bindings 399 } 400 401 # A few additional bindings of my own. 402 403 bind Text <Control-h> { 404 if {!$tk_strictMotif && [%W compare insert != 1.0]} { 405 %W delete insert-1c 406 %W see insert 407 } 408 } 409 bind Text <2> { 410 if {!$tk_strictMotif} { 411 tk::TextScanMark %W %x %y 412 } 413 } 414 bind Text <B2-Motion> { 415 if {!$tk_strictMotif} { 416 tk::TextScanDrag %W %x %y 417 } 418 } 419 set ::tk::Priv(prevPos) {} 420 421 # The MouseWheel will typically only fire on Windows and MacOS X. 422 # However, someone could use the "event generate" command to produce one 423 # on other platforms. We must be careful not to round -ve values of %D 424 # down to zero. 425 426 if {[tk windowingsystem] eq "aqua"} { 427 bind Text <MouseWheel> { 428 %W yview scroll [expr {-15 * (%D)}] pixels 429 } 430 bind Text <Option-MouseWheel> { 431 %W yview scroll [expr {-150 * (%D)}] pixels 432 } 433 bind Text <Shift-MouseWheel> { 434 %W xview scroll [expr {-15 * (%D)}] pixels 435 } 436 bind Text <Shift-Option-MouseWheel> { 437 %W xview scroll [expr {-150 * (%D)}] pixels 438 } 439 } else { 440 # We must make sure that positive and negative movements are rounded 441 # equally to integers, avoiding the problem that 442 # (int)1/3 = 0, 443 # but 444 # (int)-1/3 = -1 445 # The following code ensure equal +/- behaviour. 446 bind Text <MouseWheel> { 447 if {%D >= 0} { 448 %W yview scroll [expr {-%D/3}] pixels 449 } else { 450 %W yview scroll [expr {(2-%D)/3}] pixels 451 } 452 } 453 bind Text <Shift-MouseWheel> { 454 if {%D >= 0} { 455 %W xview scroll [expr {-%D/3}] pixels 456 } else { 457 %W xview scroll [expr {(2-%D)/3}] pixels 458 } 459 } 460 } 461 462 if {"x11" eq [tk windowingsystem]} { 463 # Support for mousewheels on Linux/Unix commonly comes through mapping 464 # the wheel to the extended buttons. If you have a mousewheel, find 465 # Linux configuration info at: 466 # http://linuxreviews.org/howtos/xfree/mouse/ 467 bind Text <4> { 468 if {!$tk_strictMotif} { 469 %W yview scroll -50 pixels 470 } 471 } 472 bind Text <5> { 473 if {!$tk_strictMotif} { 474 %W yview scroll 50 pixels 475 } 476 } 477 bind Text <Shift-4> { 478 if {!$tk_strictMotif} { 479 %W xview scroll -50 pixels 480 } 481 } 482 bind Text <Shift-5> { 483 if {!$tk_strictMotif} { 484 %W xview scroll 50 pixels 485 } 486 } 487 } 488 489 # ::tk::TextClosestGap -- 490 # Given x and y coordinates, this procedure finds the closest boundary 491 # between characters to the given coordinates and returns the index 492 # of the character just after the boundary. 493 # 494 # Arguments: 495 # w - The text window. 496 # x - X-coordinate within the window. 497 # y - Y-coordinate within the window. 498 499 proc ::tk::TextClosestGap {w x y} { 500 set pos [$w index @$x,$y] 501 set bbox [$w bbox $pos] 502 if {$bbox eq ""} { 503 return $pos 504 } 505 if {($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2)} { 506 return $pos 507 } 508 $w index "$pos + 1 char" 509 } 510 511 # ::tk::TextButton1 -- 512 # This procedure is invoked to handle button-1 presses in text 513 # widgets. It moves the insertion cursor, sets the selection anchor, 514 # and claims the input focus. 515 # 516 # Arguments: 517 # w - The text window in which the button was pressed. 518 # x - The x-coordinate of the button press. 519 # y - The x-coordinate of the button press. 520 521 proc ::tk::TextButton1 {w x y} { 522 variable ::tk::Priv 523 524 set Priv(selectMode) char 525 set Priv(mouseMoved) 0 526 set Priv(pressX) $x 527 set anchorname [tk::TextAnchor $w] 528 $w mark set insert [TextClosestGap $w $x $y] 529 $w mark set $anchorname insert 530 # Set the anchor mark's gravity depending on the click position 531 # relative to the gap 532 set bbox [$w bbox [$w index $anchorname]] 533 if {$x > [lindex $bbox 0]} { 534 $w mark gravity $anchorname right 535 } else { 536 $w mark gravity $anchorname left 537 } 538 # Allow focus in any case on Windows, because that will let the 539 # selection be displayed even for state disabled text widgets. 540 if {[tk windowingsystem] eq "win32" \ 541 || [$w cget -state] eq "normal"} { 542 focus $w 543 } 544 if {[$w cget -autoseparators]} { 545 $w edit separator 546 } 547 } 548 549 # ::tk::TextSelectTo -- 550 # This procedure is invoked to extend the selection, typically when 551 # dragging it with the mouse. Depending on the selection mode (character, 552 # word, line) it selects in different-sized units. This procedure 553 # ignores mouse motions initially until the mouse has moved from 554 # one character to another or until there have been multiple clicks. 555 # 556 # Note that the 'anchor' is implemented programmatically using 557 # a text widget mark, and uses a name that will be unique for each 558 # text widget (even when there are multiple peers). Currently the 559 # anchor is considered private to Tk, hence the name 'tk::anchor$w'. 560 # 561 # Arguments: 562 # w - The text window in which the button was pressed. 563 # x - Mouse x position. 564 # y - Mouse y position. 565 566 set ::tk::Priv(textanchoruid) 0 567 568 proc ::tk::TextAnchor {w} { 569 variable Priv 570 if {![info exists Priv(textanchor,$w)]} { 571 set Priv(textanchor,$w) tk::anchor[incr Priv(textanchoruid)] 572 } 573 return $Priv(textanchor,$w) 574 } 575 576 proc ::tk::TextSelectTo {w x y {extend 0}} { 577 variable ::tk::Priv 578 579 set anchorname [tk::TextAnchor $w] 580 set cur [TextClosestGap $w $x $y] 581 if {[catch {$w index $anchorname}]} { 582 $w mark set $anchorname $cur 583 } 584 set anchor [$w index $anchorname] 585 if {[$w compare $cur != $anchor] || (abs($Priv(pressX) - $x) >= 3)} { 586 set Priv(mouseMoved) 1 587 } 588 switch -- $Priv(selectMode) { 589 char { 590 if {[$w compare $cur < $anchorname]} { 591 set first $cur 592 set last $anchorname 593 } else { 594 set first $anchorname 595 set last $cur 596 } 597 } 598 word { 599 # Set initial range based only on the anchor (1 char min width) 600 if {[$w mark gravity $anchorname] eq "right"} { 601 set first $anchorname 602 set last "$anchorname + 1c" 603 } else { 604 set first "$anchorname - 1c" 605 set last $anchorname 606 } 607 # Extend range (if necessary) based on the current point 608 if {[$w compare $cur < $first]} { 609 set first $cur 610 } elseif {[$w compare $cur > $last]} { 611 set last $cur 612 } 613 614 # Now find word boundaries 615 set first [TextPrevPos $w "$first + 1c" tcl_wordBreakBefore] 616 set last [TextNextPos $w "$last - 1c" tcl_wordBreakAfter] 617 } 618 line { 619 # Set initial range based only on the anchor 620 set first "$anchorname linestart" 621 set last "$anchorname lineend" 622 623 # Extend range (if necessary) based on the current point 624 if {[$w compare $cur < $first]} { 625 set first "$cur linestart" 626 } elseif {[$w compare $cur > $last]} { 627 set last "$cur lineend" 628 } 629 set first [$w index $first] 630 set last [$w index "$last + 1c"] 631 } 632 } 633 if {$Priv(mouseMoved) || ($Priv(selectMode) ne "char")} { 634 $w tag remove sel 0.0 end 635 $w mark set insert $cur 636 $w tag add sel $first $last 637 $w tag remove sel $last end 638 update idletasks 639 } 640 } 641 642 # ::tk::TextKeyExtend -- 643 # This procedure handles extending the selection from the keyboard, 644 # where the point to extend to is really the boundary between two 645 # characters rather than a particular character. 646 # 647 # Arguments: 648 # w - The text window. 649 # index - The point to which the selection is to be extended. 650 651 proc ::tk::TextKeyExtend {w index} { 652 653 set anchorname [tk::TextAnchor $w] 654 set cur [$w index $index] 655 if {[catch {$w index $anchorname}]} { 656 $w mark set $anchorname $cur 657 } 658 set anchor [$w index $anchorname] 659 if {[$w compare $cur < $anchorname]} { 660 set first $cur 661 set last $anchorname 662 } else { 663 set first $anchorname 664 set last $cur 665 } 666 $w tag remove sel 0.0 $first 667 $w tag add sel $first $last 668 $w tag remove sel $last end 669 } 670 671 # ::tk::TextPasteSelection -- 672 # This procedure sets the insertion cursor to the mouse position, 673 # inserts the selection, and sets the focus to the window. 674 # 675 # Arguments: 676 # w - The text window. 677 # x, y - Position of the mouse. 678 679 proc ::tk::TextPasteSelection {w x y} { 680 $w mark set insert [TextClosestGap $w $x $y] 681 if {![catch {::tk::GetSelection $w PRIMARY} sel]} { 682 set oldSeparator [$w cget -autoseparators] 683 if {$oldSeparator} { 684 $w configure -autoseparators 0 685 $w edit separator 686 } 687 $w insert insert $sel 688 if {$oldSeparator} { 689 $w edit separator 690 $w configure -autoseparators 1 691 } 692 } 693 if {[$w cget -state] eq "normal"} { 694 focus $w 695 } 696 } 697 698 # ::tk::TextAutoScan -- 699 # This procedure is invoked when the mouse leaves a text window 700 # with button 1 down. It scrolls the window up, down, left, or right, 701 # depending on where the mouse is (this information was saved in 702 # ::tk::Priv(x) and ::tk::Priv(y)), and reschedules itself as an "after" 703 # command so that the window continues to scroll until the mouse 704 # moves back into the window or the mouse button is released. 705 # 706 # Arguments: 707 # w - The text window. 708 709 proc ::tk::TextAutoScan {w} { 710 variable ::tk::Priv 711 if {![winfo exists $w]} { 712 return 713 } 714 if {$Priv(y) >= [winfo height $w]} { 715 $w yview scroll [expr {1 + $Priv(y) - [winfo height $w]}] pixels 716 } elseif {$Priv(y) < 0} { 717 $w yview scroll [expr {-1 + $Priv(y)}] pixels 718 } elseif {$Priv(x) >= [winfo width $w]} { 719 $w xview scroll 2 units 720 } elseif {$Priv(x) < 0} { 721 $w xview scroll -2 units 722 } else { 723 return 724 } 725 TextSelectTo $w $Priv(x) $Priv(y) 726 set Priv(afterId) [after 50 [list tk::TextAutoScan $w]] 727 } 728 729 # ::tk::TextSetCursor 730 # Move the insertion cursor to a given position in a text. Also 731 # clears the selection, if there is one in the text, and makes sure 732 # that the insertion cursor is visible. Also, don't let the insertion 733 # cursor appear on the dummy last line of the text. 734 # 735 # Arguments: 736 # w - The text window. 737 # pos - The desired new position for the cursor in the window. 738 739 proc ::tk::TextSetCursor {w pos} { 740 if {[$w compare $pos == end]} { 741 set pos {end - 1 chars} 742 } 743 $w mark set insert $pos 744 $w tag remove sel 1.0 end 745 $w see insert 746 if {[$w cget -autoseparators]} { 747 $w edit separator 748 } 749 } 750 751 # ::tk::TextKeySelect 752 # This procedure is invoked when stroking out selections using the 753 # keyboard. It moves the cursor to a new position, then extends 754 # the selection to that position. 755 # 756 # Arguments: 757 # w - The text window. 758 # new - A new position for the insertion cursor (the cursor hasn't 759 # actually been moved to this position yet). 760 761 proc ::tk::TextKeySelect {w new} { 762 set anchorname [tk::TextAnchor $w] 763 if {[$w tag nextrange sel 1.0 end] eq ""} { 764 if {[$w compare $new < insert]} { 765 $w tag add sel $new insert 766 } else { 767 $w tag add sel insert $new 768 } 769 $w mark set $anchorname insert 770 } else { 771 if {[catch {$w index $anchorname}]} { 772 $w mark set $anchorname insert 773 } 774 if {[$w compare $new < $anchorname]} { 775 set first $new 776 set last $anchorname 777 } else { 778 set first $anchorname 779 set last $new 780 } 781 $w tag remove sel 1.0 $first 782 $w tag add sel $first $last 783 $w tag remove sel $last end 784 } 785 $w mark set insert $new 786 $w see insert 787 update idletasks 788 } 789 790 # ::tk::TextResetAnchor -- 791 # Set the selection anchor to whichever end is farthest from the 792 # index argument. One special trick: if the selection has two or 793 # fewer characters, just leave the anchor where it is. In this 794 # case it doesn't matter which point gets chosen for the anchor, 795 # and for the things like Shift-Left and Shift-Right this produces 796 # better behavior when the cursor moves back and forth across the 797 # anchor. 798 # 799 # Arguments: 800 # w - The text widget. 801 # index - Position at which mouse button was pressed, which determines 802 # which end of selection should be used as anchor point. 803 804 proc ::tk::TextResetAnchor {w index} { 805 if {[$w tag ranges sel] eq ""} { 806 # Don't move the anchor if there is no selection now; this 807 # makes the widget behave "correctly" when the user clicks 808 # once, then shift-clicks somewhere -- ie, the area between 809 # the two clicks will be selected. [Bug: 5929]. 810 return 811 } 812 set anchorname [tk::TextAnchor $w] 813 set a [$w index $index] 814 set b [$w index sel.first] 815 set c [$w index sel.last] 816 if {[$w compare $a < $b]} { 817 $w mark set $anchorname sel.last 818 return 819 } 820 if {[$w compare $a > $c]} { 821 $w mark set $anchorname sel.first 822 return 823 } 824 scan $a "%d.%d" lineA chA 825 scan $b "%d.%d" lineB chB 826 scan $c "%d.%d" lineC chC 827 if {$lineB < $lineC+2} { 828 set total [string length [$w get $b $c]] 829 if {$total <= 2} { 830 return 831 } 832 if {[string length [$w get $b $a]] < ($total/2)} { 833 $w mark set $anchorname sel.last 834 } else { 835 $w mark set $anchorname sel.first 836 } 837 return 838 } 839 if {($lineA-$lineB) < ($lineC-$lineA)} { 840 $w mark set $anchorname sel.last 841 } else { 842 $w mark set $anchorname sel.first 843 } 844 } 845 846 # ::tk::TextCursorInSelection -- 847 # Check whether the selection exists and contains the insertion cursor. Note 848 # that it assumes that the selection is contiguous. 849 # 850 # Arguments: 851 # w - The text widget whose selection is to be checked 852 853 proc ::tk::TextCursorInSelection {w} { 854 expr { 855 [llength [$w tag ranges sel]] 856 && [$w compare sel.first <= insert] 857 && [$w compare sel.last >= insert] 858 } 859 } 860 861 # ::tk::TextInsert -- 862 # Insert a string into a text at the point of the insertion cursor. 863 # If there is a selection in the text, and it covers the point of the 864 # insertion cursor, then delete the selection before inserting. 865 # 866 # Arguments: 867 # w - The text window in which to insert the string 868 # s - The string to insert (usually just a single character) 869 870 proc ::tk::TextInsert {w s} { 871 if {$s eq "" || [$w cget -state] eq "disabled"} { 872 return 873 } 874 set compound 0 875 if {[TextCursorInSelection $w]} { 876 set oldSeparator [$w cget -autoseparators] 877 if {$oldSeparator} { 878 $w configure -autoseparators 0 879 $w edit separator 880 set compound 1 881 } 882 $w delete sel.first sel.last 883 } 884 $w insert insert $s 885 $w see insert 886 if {$compound && $oldSeparator} { 887 $w edit separator 888 $w configure -autoseparators 1 889 } 890 } 891 892 # ::tk::TextUpDownLine -- 893 # Returns the index of the character one display line above or below the 894 # insertion cursor. There are two tricky things here. First, we want to 895 # maintain the original x position across repeated operations, even though 896 # some lines that will get passed through don't have enough characters to 897 # cover the original column. Second, don't try to scroll past the 898 # beginning or end of the text. 899 # 900 # Arguments: 901 # w - The text window in which the cursor is to move. 902 # n - The number of display lines to move: -1 for up one line, 903 # +1 for down one line. 904 905 proc ::tk::TextUpDownLine {w n} { 906 variable ::tk::Priv 907 908 set i [$w index insert] 909 if {$Priv(prevPos) ne $i} { 910 set Priv(textPosOrig) $i 911 } 912 set lines [$w count -displaylines $Priv(textPosOrig) $i] 913 set new [$w index \ 914 "$Priv(textPosOrig) + [expr {$lines + $n}] displaylines"] 915 if {[$w compare $new == end] \ 916 || [$w compare $new == "insert display linestart"]} { 917 set new $i 918 } 919 set Priv(prevPos) $new 920 return $new 921 } 922 923 # ::tk::TextPrevPara -- 924 # Returns the index of the beginning of the paragraph just before a given 925 # position in the text (the beginning of a paragraph is the first non-blank 926 # character after a blank line). 927 # 928 # Arguments: 929 # w - The text window in which the cursor is to move. 930 # pos - Position at which to start search. 931 932 proc ::tk::TextPrevPara {w pos} { 933 set pos [$w index "$pos linestart"] 934 while {1} { 935 if {([$w get "$pos - 1 line"] eq "\n" && ([$w get $pos] ne "\n")) \ 936 || $pos eq "1.0"} { 937 if {[regexp -indices -- {^[ \t]+(.)} \ 938 [$w get $pos "$pos lineend"] -> index]} { 939 set pos [$w index "$pos + [lindex $index 0] chars"] 940 } 941 if {[$w compare $pos != insert] || [lindex [split $pos .] 0]==1} { 942 return $pos 943 } 944 } 945 set pos [$w index "$pos - 1 line"] 946 } 947 } 948 949 # ::tk::TextNextPara -- 950 # Returns the index of the beginning of the paragraph just after a given 951 # position in the text (the beginning of a paragraph is the first non-blank 952 # character after a blank line). 953 # 954 # Arguments: 955 # w - The text window in which the cursor is to move. 956 # start - Position at which to start search. 957 958 proc ::tk::TextNextPara {w start} { 959 set pos [$w index "$start linestart + 1 line"] 960 while {[$w get $pos] ne "\n"} { 961 if {[$w compare $pos == end]} { 962 return [$w index "end - 1c"] 963 } 964 set pos [$w index "$pos + 1 line"] 965 } 966 while {[$w get $pos] eq "\n"} { 967 set pos [$w index "$pos + 1 line"] 968 if {[$w compare $pos == end]} { 969 return [$w index "end - 1c"] 970 } 971 } 972 if {[regexp -indices -- {^[ \t]+(.)} \ 973 [$w get $pos "$pos lineend"] -> index]} { 974 return [$w index "$pos + [lindex $index 0] chars"] 975 } 976 return $pos 977 } 978 979 # ::tk::TextScrollPages -- 980 # This is a utility procedure used in bindings for moving up and down 981 # pages and possibly extending the selection along the way. It scrolls 982 # the view in the widget by the number of pages, and it returns the 983 # index of the character that is at the same position in the new view 984 # as the insertion cursor used to be in the old view. 985 # 986 # Arguments: 987 # w - The text window in which the cursor is to move. 988 # count - Number of pages forward to scroll; may be negative 989 # to scroll backwards. 990 991 proc ::tk::TextScrollPages {w count} { 992 set bbox [$w bbox insert] 993 $w yview scroll $count pages 994 if {$bbox eq ""} { 995 return [$w index @[expr {[winfo height $w]/2}],0] 996 } 997 return [$w index @[lindex $bbox 0],[lindex $bbox 1]] 998 } 999 1000 # ::tk::TextTranspose -- 1001 # This procedure implements the "transpose" function for text widgets. 1002 # It tranposes the characters on either side of the insertion cursor, 1003 # unless the cursor is at the end of the line. In this case it 1004 # transposes the two characters to the left of the cursor. In either 1005 # case, the cursor ends up to the right of the transposed characters. 1006 # 1007 # Arguments: 1008 # w - Text window in which to transpose. 1009 1010 proc ::tk::TextTranspose w { 1011 set pos insert 1012 if {[$w compare $pos != "$pos lineend"]} { 1013 set pos [$w index "$pos + 1 char"] 1014 } 1015 set new [$w get "$pos - 1 char"][$w get "$pos - 2 char"] 1016 if {[$w compare "$pos - 1 char" == 1.0]} { 1017 return 1018 } 1019 # ensure this is seen as an atomic op to undo 1020 set autosep [$w cget -autoseparators] 1021 if {$autosep} { 1022 $w configure -autoseparators 0 1023 $w edit separator 1024 } 1025 $w delete "$pos - 2 char" $pos 1026 $w insert insert $new 1027 $w see insert 1028 if {$autosep} { 1029 $w edit separator 1030 $w configure -autoseparators $autosep 1031 } 1032 } 1033 1034 # ::tk_textCopy -- 1035 # This procedure copies the selection from a text widget into the 1036 # clipboard. 1037 # 1038 # Arguments: 1039 # w - Name of a text widget. 1040 1041 proc ::tk_textCopy w { 1042 if {![catch {set data [$w get sel.first sel.last]}]} { 1043 clipboard clear -displayof $w 1044 clipboard append -displayof $w $data 1045 } 1046 } 1047 1048 # ::tk_textCut -- 1049 # This procedure copies the selection from a text widget into the 1050 # clipboard, then deletes the selection (if it exists in the given 1051 # widget). 1052 # 1053 # Arguments: 1054 # w - Name of a text widget. 1055 1056 proc ::tk_textCut w { 1057 if {![catch {set data [$w get sel.first sel.last]}]} { 1058 # make <<Cut>> an atomic operation on the Undo stack, 1059 # i.e. separate it from other delete operations on either side 1060 set oldSeparator [$w cget -autoseparators] 1061 if {$oldSeparator} { 1062 $w edit separator 1063 } 1064 clipboard clear -displayof $w 1065 clipboard append -displayof $w $data 1066 $w delete sel.first sel.last 1067 if {$oldSeparator} { 1068 $w edit separator 1069 } 1070 } 1071 } 1072 1073 # ::tk_textPaste -- 1074 # This procedure pastes the contents of the clipboard to the insertion 1075 # point in a text widget. 1076 # 1077 # Arguments: 1078 # w - Name of a text widget. 1079 1080 proc ::tk_textPaste w { 1081 if {![catch {::tk::GetSelection $w CLIPBOARD} sel]} { 1082 set oldSeparator [$w cget -autoseparators] 1083 if {$oldSeparator} { 1084 $w configure -autoseparators 0 1085 $w edit separator 1086 } 1087 if {[tk windowingsystem] ne "x11"} { 1088 catch { $w delete sel.first sel.last } 1089 } 1090 $w insert insert $sel 1091 if {$oldSeparator} { 1092 $w edit separator 1093 $w configure -autoseparators 1 1094 } 1095 } 1096 } 1097 1098 # ::tk::TextNextWord -- 1099 # Returns the index of the next word position after a given position in the 1100 # text. The next word is platform dependent and may be either the next 1101 # end-of-word position or the next start-of-word position after the next 1102 # end-of-word position. 1103 # 1104 # Arguments: 1105 # w - The text window in which the cursor is to move. 1106 # start - Position at which to start search. 1107 1108 if {[tk windowingsystem] eq "win32"} { 1109 proc ::tk::TextNextWord {w start} { 1110 TextNextPos $w [TextNextPos $w $start tcl_endOfWord] \ 1111 tcl_startOfNextWord 1112 } 1113 } else { 1114 proc ::tk::TextNextWord {w start} { 1115 TextNextPos $w $start tcl_endOfWord 1116 } 1117 } 1118 1119 # ::tk::TextNextPos -- 1120 # Returns the index of the next position after the given starting 1121 # position in the text as computed by a specified function. 1122 # 1123 # Arguments: 1124 # w - The text window in which the cursor is to move. 1125 # start - Position at which to start search. 1126 # op - Function to use to find next position. 1127 1128 proc ::tk::TextNextPos {w start op} { 1129 set text "" 1130 set cur $start 1131 while {[$w compare $cur < end]} { 1132 set text $text[$w get -displaychars $cur "$cur lineend + 1c"] 1133 set pos [$op $text 0] 1134 if {$pos >= 0} { 1135 return [$w index "$start + $pos display chars"] 1136 } 1137 set cur [$w index "$cur lineend +1c"] 1138 } 1139 return end 1140 } 1141 1142 # ::tk::TextPrevPos -- 1143 # Returns the index of the previous position before the given starting 1144 # position in the text as computed by a specified function. 1145 # 1146 # Arguments: 1147 # w - The text window in which the cursor is to move. 1148 # start - Position at which to start search. 1149 # op - Function to use to find next position. 1150 1151 proc ::tk::TextPrevPos {w start op} { 1152 set text "" 1153 set cur $start 1154 while {[$w compare $cur > 0.0]} { 1155 set text [$w get -displaychars "$cur linestart - 1c" $cur]$text 1156 set pos [$op $text end] 1157 if {$pos >= 0} { 1158 return [$w index "$cur linestart - 1c + $pos display chars"] 1159 } 1160 set cur [$w index "$cur linestart - 1c"] 1161 } 1162 return 0.0 1163 } 1164 1165 # ::tk::TextScanMark -- 1166 # 1167 # Marks the start of a possible scan drag operation 1168 # 1169 # Arguments: 1170 # w - The text window from which the text to get 1171 # x - x location on screen 1172 # y - y location on screen 1173 1174 proc ::tk::TextScanMark {w x y} { 1175 variable ::tk::Priv 1176 $w scan mark $x $y 1177 set Priv(x) $x 1178 set Priv(y) $y 1179 set Priv(mouseMoved) 0 1180 } 1181 1182 # ::tk::TextScanDrag -- 1183 # 1184 # Marks the start of a possible scan drag operation 1185 # 1186 # Arguments: 1187 # w - The text window from which the text to get 1188 # x - x location on screen 1189 # y - y location on screen 1190 1191 proc ::tk::TextScanDrag {w x y} { 1192 variable ::tk::Priv 1193 # Make sure these exist, as some weird situations can trigger the 1194 # motion binding without the initial press. [Bug #220269] 1195 if {![info exists Priv(x)]} { 1196 set Priv(x) $x 1197 } 1198 if {![info exists Priv(y)]} { 1199 set Priv(y) $y 1200 } 1201 if {($x != $Priv(x)) || ($y != $Priv(y))} { 1202 set Priv(mouseMoved) 1 1203 } 1204 if {[info exists Priv(mouseMoved)] && $Priv(mouseMoved)} { 1205 $w scan dragto $x $y 1206 } 1207 }