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