1
# $XConsortium: text.tcl /main/1 1996/09/21 14:16:15 kaleb $
6
# $XFree86: xc/programs/Xserver/hw/xfree86/XF86Setup/tcllib/text.tcl,v 3.1 1996/12/27 06:55:08 dawes Exp $
10
# This file defines the default bindings for Tk text widgets and provides
11
# procedures that help in implementing the bindings.
13
# @(#) text.tcl 1.36 95/06/28 10:24:23
15
# Copyright (c) 1992-1994 The Regents of the University of California.
16
# Copyright (c) 1994-1995 Sun Microsystems, Inc.
18
# See the file "license.terms" for information on usage and redistribution
19
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
22
#-------------------------------------------------------------------------
23
# Elements of tkPriv that are used in this file:
25
# afterId - If non-null, it means that auto-scanning is underway
26
# and it gives the "after" id for the next auto-scan
27
# command to be executed.
28
# char - Character position on the line; kept in order
29
# to allow moving up or down past short lines while
30
# still remembering the desired position.
31
# mouseMoved - Non-zero means the mouse has moved a significant
32
# amount since the button went down (so, for example,
33
# start dragging out a selection).
34
# prevPos - Used when moving up or down lines via the keyboard.
35
# Keeps track of the previous insert position, so
36
# we can distinguish a series of ups and downs, all
37
# in a row, from a new up or down.
38
# selectMode - The style of selection currently underway:
39
# char, word, or line.
40
# x, y - Last known mouse coordinates for scanning
42
#-------------------------------------------------------------------------
44
# tkTextClipboardKeysyms --
45
# This procedure is invoked to identify the keys that correspond to
46
# the "copy", "cut", and "paste" functions for the clipboard.
49
# copy - Name of the key (keysym name plus modifiers, if any,
50
# such as "Meta-y") used for the copy operation.
51
# cut - Name of the key used for the cut operation.
52
# paste - Name of the key used for the paste operation.
54
proc tkTextClipboardKeysyms {copy cut paste} {
56
if {[selection own -displayof %W] == "%W"} {
57
clipboard clear -displayof %W
59
clipboard append -displayof %W [selection get -displayof %W]
64
if {[selection own -displayof %W] == "%W"} {
65
clipboard clear -displayof %W
67
clipboard append -displayof %W [selection get -displayof %W]
68
%W delete sel.first sel.last
74
%W insert insert [selection get -displayof %W \
80
#-------------------------------------------------------------------------
81
# The code below creates the default class bindings for entries.
82
#-------------------------------------------------------------------------
84
# Standard Motif bindings:
87
tkTextButton1 %W %x %y
88
%W tag remove sel 0.0 end
90
bind Text <B1-Motion> {
93
tkTextSelectTo %W %x %y
95
bind Text <Double-1> {
96
set tkPriv(selectMode) word
97
tkTextSelectTo %W %x %y
98
catch {%W mark set insert sel.first}
100
bind Text <Triple-1> {
101
set tkPriv(selectMode) line
102
tkTextSelectTo %W %x %y
103
catch {%W mark set insert sel.first}
105
bind Text <Shift-1> {
106
tkTextResetAnchor %W @%x,%y
107
set tkPriv(selectMode) char
108
tkTextSelectTo %W %x %y
110
bind Text <Double-Shift-1> {
111
set tkPriv(selectMode) word
112
tkTextSelectTo %W %x %y
114
bind Text <Triple-Shift-1> {
115
set tkPriv(selectMode) line
116
tkTextSelectTo %W %x %y
118
bind Text <B1-Leave> {
123
bind Text <B1-Enter> {
126
bind Text <ButtonRelease-1> {
129
bind Text <Control-1> {
130
%W mark set insert @%x,%y
133
tkTextSetCursor %W [%W index {insert - 1c}]
136
tkTextSetCursor %W [%W index {insert + 1c}]
139
tkTextSetCursor %W [tkTextUpDownLine %W -1]
142
tkTextSetCursor %W [tkTextUpDownLine %W 1]
144
bind Text <Shift-Left> {
145
tkTextKeySelect %W [%W index {insert - 1c}]
147
bind Text <Shift-Right> {
148
tkTextKeySelect %W [%W index {insert + 1c}]
150
bind Text <Shift-Up> {
151
tkTextKeySelect %W [tkTextUpDownLine %W -1]
153
bind Text <Shift-Down> {
154
tkTextKeySelect %W [tkTextUpDownLine %W 1]
156
bind Text <Control-Left> {
157
tkTextSetCursor %W [%W index {insert - 1c wordstart}]
159
bind Text <Control-Right> {
160
tkTextSetCursor %W [%W index {insert wordend}]
162
bind Text <Control-Up> {
163
tkTextSetCursor %W [tkTextPrevPara %W insert]
165
bind Text <Control-Down> {
166
tkTextSetCursor %W [tkTextNextPara %W insert]
168
bind Text <Shift-Control-Left> {
169
tkTextKeySelect %W [%W index {insert - 1c wordstart}]
171
bind Text <Shift-Control-Right> {
172
tkTextKeySelect %W [%W index {insert wordend}]
174
bind Text <Shift-Control-Up> {
175
tkTextKeySelect %W [tkTextPrevPara %W insert]
177
bind Text <Shift-Control-Down> {
178
tkTextKeySelect %W [tkTextNextPara %W insert]
181
tkTextSetCursor %W [tkTextScrollPages %W -1]
183
bind Text <Shift-Prior> {
184
tkTextKeySelect %W [tkTextScrollPages %W -1]
187
tkTextSetCursor %W [tkTextScrollPages %W 1]
189
bind Text <Shift-Next> {
190
tkTextKeySelect %W [tkTextScrollPages %W 1]
192
bind Text <Control-Prior> {
193
%W xview scroll -1 page
195
bind Text <Control-Next> {
196
%W xview scroll 1 page
200
tkTextSetCursor %W {insert linestart}
202
bind Text <Shift-Home> {
203
tkTextKeySelect %W {insert linestart}
206
tkTextSetCursor %W {insert lineend}
208
bind Text <Shift-End> {
209
tkTextKeySelect %W {insert lineend}
211
bind Text <Control-Home> {
212
tkTextSetCursor %W 1.0
214
bind Text <Control-Shift-Home> {
215
tkTextKeySelect %W 1.0
217
bind Text <Control-End> {
218
tkTextSetCursor %W {end - 1 char}
220
bind Text <Control-Shift-End> {
221
tkTextKeySelect %W {end - 1 char}
229
bind Text <Shift-Tab> {
230
# Needed only to keep <Tab> binding from triggering; doesn't
231
# have to actually do anything.
233
bind Text <Control-Tab> {
234
focus [tk_focusNext %W]
236
bind Text <Control-Shift-Tab> {
237
focus [tk_focusPrev %W]
239
bind Text <Control-i> {
246
if {[%W tag nextrange sel 1.0 end] != ""} {
247
%W delete sel.first sel.last
253
bind Text <BackSpace> {
254
if {[%W tag nextrange sel 1.0 end] != ""} {
255
%W delete sel.first sel.last
256
} elseif [%W compare insert != 1.0] {
262
bind Text <Control-space> {
263
%W mark set anchor insert
266
%W mark set anchor insert
268
bind Text <Control-Shift-space> {
269
set tkPriv(selectMode) char
270
tkTextKeyExtend %W insert
272
bind Text <Shift-Select> {
273
set tkPriv(selectMode) char
274
tkTextKeyExtend %W insert
276
bind Text <Control-slash> {
277
%W tag add sel 1.0 end
279
bind Text <Control-backslash> {
280
%W tag remove sel 1.0 end
282
tkTextClipboardKeysyms F16 F20 F18
284
catch {tkTextInsert %W [selection get -displayof %W]}
286
bind Text <KeyPress> {
290
# Ignore all Alt, Meta, and Control keypresses unless explicitly bound.
291
# Otherwise, if a widget binding for one of these is defined, the
292
# <KeyPress> class binding will also fire and insert the character,
293
# which is wrong. Ditto for <Escape>.
295
bind Text <Alt-KeyPress> {# nothing }
296
bind Text <Meta-KeyPress> {# nothing}
297
bind Text <Control-KeyPress> {# nothing}
298
bind Text <Escape> {# nothing}
299
bind Text <KP_Enter> {# nothing}
301
# Additional emacs-like bindings:
303
if !$tk_strictMotif {
304
bind Text <Control-a> {
305
tkTextSetCursor %W {insert linestart}
307
bind Text <Control-b> {
308
tkTextSetCursor %W insert-1c
310
bind Text <Control-d> {
313
bind Text <Control-e> {
314
tkTextSetCursor %W {insert lineend}
316
bind Text <Control-f> {
317
tkTextSetCursor %W insert+1c
319
bind Text <Control-k> {
320
if [%W compare insert == {insert lineend}] {
323
%W delete insert {insert lineend}
326
bind Text <Control-n> {
327
tkTextSetCursor %W [tkTextUpDownLine %W 1]
329
bind Text <Control-o> {
331
%W mark set insert insert-1c
333
bind Text <Control-p> {
334
tkTextSetCursor %W [tkTextUpDownLine %W -1]
336
bind Text <Control-t> {
339
bind Text <Control-v> {
340
tkTextScrollPages %W 1
343
tkTextSetCursor %W {insert - 1c wordstart}
346
%W delete insert {insert wordend}
349
tkTextSetCursor %W {insert wordend}
351
bind Text <Meta-less> {
352
tkTextSetCursor %W 1.0
354
bind Text <Meta-greater> {
355
tkTextSetCursor %W end-1c
357
bind Text <Meta-BackSpace> {
358
%W delete {insert -1c wordstart} insert
360
bind Text <Meta-Delete> {
361
%W delete {insert -1c wordstart} insert
363
tkTextClipboardKeysyms Meta-w Control-w Control-y
365
# A few additional bindings of my own.
367
bind Text <Control-h> {
368
if [%W compare insert != 1.0] {
377
set tkPriv(mouseMoved) 0
379
bind Text <B2-Motion> {
380
if {(%x != $tkPriv(x)) || (%y != $tkPriv(y))} {
381
set tkPriv(mouseMoved) 1
383
if $tkPriv(mouseMoved) {
387
bind Text <ButtonRelease-2> {
388
if !$tkPriv(mouseMoved) {
390
%W insert @%x,%y [selection get -displayof %W]
395
set tkPriv(prevPos) {}
398
# This procedure is invoked to handle button-1 presses in text
399
# widgets. It moves the insertion cursor, sets the selection anchor,
400
# and claims the input focus.
403
# w - The text window in which the button was pressed.
404
# x - The x-coordinate of the button press.
405
# y - The x-coordinate of the button press.
407
proc tkTextButton1 {w x y} {
410
set tkPriv(selectMode) char
411
set tkPriv(mouseMoved) 0
412
set tkPriv(pressX) $x
413
$w mark set insert @$x,$y
414
$w mark set anchor insert
415
if {[$w cget -state] == "normal"} {focus $w}
419
# This procedure is invoked to extend the selection, typically when
420
# dragging it with the mouse. Depending on the selection mode (character,
421
# word, line) it selects in different-sized units. This procedure
422
# ignores mouse motions initially until the mouse has moved from
423
# one character to another or until there have been multiple clicks.
426
# w - The text window in which the button was pressed.
427
# x - Mouse x position.
428
# y - Mouse y position.
430
proc tkTextSelectTo {w x y} {
433
set cur [$w index @$x,$y]
434
if [catch {$w index anchor}] {
435
$w mark set anchor $cur
437
set anchor [$w index anchor]
438
if {[$w compare $cur != $anchor] || (abs($tkPriv(pressX) - $x) >= 3)} {
439
set tkPriv(mouseMoved) 1
441
switch $tkPriv(selectMode) {
443
if [$w compare $cur < anchor] {
448
set last [$w index "$cur + 1c"]
452
if [$w compare $cur < anchor] {
453
set first [$w index "$cur wordstart"]
454
set last [$w index "anchor - 1c wordend"]
456
set first [$w index "anchor wordstart"]
457
set last [$w index "$cur wordend"]
461
if [$w compare $cur < anchor] {
462
set first [$w index "$cur linestart"]
463
set last [$w index "anchor - 1c lineend + 1c"]
465
set first [$w index "anchor linestart"]
466
set last [$w index "$cur lineend + 1c"]
470
if {$tkPriv(mouseMoved) || ($tkPriv(selectMode) != "char")} {
471
$w tag remove sel 0.0 $first
472
$w tag add sel $first $last
473
$w tag remove sel $last end
479
# This procedure handles extending the selection from the keyboard,
480
# where the point to extend to is really the boundary between two
481
# characters rather than a particular character.
484
# w - The text window.
485
# index - The point to which the selection is to be extended.
487
proc tkTextKeyExtend {w index} {
490
set cur [$w index $index]
491
if [catch {$w index anchor}] {
492
$w mark set anchor $cur
494
set anchor [$w index anchor]
495
if [$w compare $cur < anchor] {
502
$w tag remove sel 0.0 $first
503
$w tag add sel $first $last
504
$w tag remove sel $last end
508
# This procedure is invoked when the mouse leaves a text window
509
# with button 1 down. It scrolls the window up, down, left, or right,
510
# depending on where the mouse is (this information was saved in
511
# tkPriv(x) and tkPriv(y)), and reschedules itself as an "after"
512
# command so that the window continues to scroll until the mouse
513
# moves back into the window or the mouse button is released.
516
# w - The text window.
518
proc tkTextAutoScan {w} {
520
if {$tkPriv(y) >= [winfo height $w]} {
521
$w yview scroll 2 units
522
} elseif {$tkPriv(y) < 0} {
523
$w yview scroll -2 units
524
} elseif {$tkPriv(x) >= [winfo width $w]} {
525
$w xview scroll 2 units
526
} elseif {$tkPriv(x) < 0} {
527
$w xview scroll -2 units
531
tkTextSelectTo $w $tkPriv(x) $tkPriv(y)
532
set tkPriv(afterId) [after 50 tkTextAutoScan $w]
536
# Move the insertion cursor to a given position in a text. Also
537
# clears the selection, if there is one in the text, and makes sure
538
# that the insertion cursor is visible. Also, don't let the insertion
539
# cursor appear on the dummy last line of the text.
542
# w - The text window.
543
# pos - The desired new position for the cursor in the window.
545
proc tkTextSetCursor {w pos} {
548
if [$w compare $pos == end] {
549
set pos {end - 1 chars}
551
$w mark set insert $pos
552
$w tag remove sel 1.0 end
557
# This procedure is invoked when stroking out selections using the
558
# keyboard. It moves the cursor to a new position, then extends
559
# the selection to that position.
562
# w - The text window.
563
# new - A new position for the insertion cursor (the cursor hasn't
564
# actually been moved to this position yet).
566
proc tkTextKeySelect {w new} {
569
if {[$w tag nextrange sel 1.0 end] == ""} {
570
if [$w compare $new < insert] {
571
$w tag add sel $new insert
573
$w tag add sel insert $new
575
$w mark set anchor insert
577
if [$w compare $new < anchor] {
584
$w tag remove sel 1.0 $first
585
$w tag add sel $first $last
586
$w tag remove sel $last end
588
$w mark set insert $new
593
# tkTextResetAnchor --
594
# Set the selection anchor to whichever end is farthest from the
595
# index argument. One special trick: if the selection has two or
596
# fewer characters, just leave the anchor where it is. In this
597
# case it doesn't matter which point gets chosen for the anchor,
598
# and for the things like Shift-Left and Shift-Right this produces
599
# better behavior when the cursor moves back and forth across the
603
# w - The text widget.
604
# index - Position at which mouse button was pressed, which determines
605
# which end of selection should be used as anchor point.
607
proc tkTextResetAnchor {w index} {
610
if {[$w tag ranges sel] == ""} {
611
$w mark set anchor $index
614
set a [$w index $index]
615
set b [$w index sel.first]
616
set c [$w index sel.last]
617
if [$w compare $a < $b] {
618
$w mark set anchor sel.last
621
if [$w compare $a > $c] {
622
$w mark set anchor sel.first
625
scan $a "%d.%d" lineA chA
626
scan $b "%d.%d" lineB chB
627
scan $c "%d.%d" lineC chC
628
if {$lineB < $lineC+2} {
629
set total [string length [$w get $b $c]]
633
if {[string length [$w get $b $a]] < ($total/2)} {
634
$w mark set anchor sel.last
636
$w mark set anchor sel.first
640
if {($lineA-$lineB) < ($lineC-$lineA)} {
641
$w mark set anchor sel.last
643
$w mark set anchor sel.first
648
# Insert a string into a text at the point of the insertion cursor.
649
# If there is a selection in the text, and it covers the point of the
650
# insertion cursor, then delete the selection before inserting.
653
# w - The text window in which to insert the string
654
# s - The string to insert (usually just a single character)
656
proc tkTextInsert {w s} {
657
if {($s == "") || ([$w cget -state] == "disabled")} {
661
if {[$w compare sel.first <= insert]
662
&& [$w compare sel.last >= insert]} {
663
$w delete sel.first sel.last
670
# tkTextUpDownLine --
671
# Returns the index of the character one line above or below the
672
# insertion cursor. There are two tricky things here. First,
673
# we want to maintain the original column across repeated operations,
674
# even though some lines that will get passed through don't have
675
# enough characters to cover the original column. Second, don't
676
# try to scroll past the beginning or end of the text.
679
# w - The text window in which the cursor is to move.
680
# n - The number of lines to move: -1 for up one line,
681
# +1 for down one line.
683
proc tkTextUpDownLine {w n} {
686
set i [$w index insert]
687
scan $i "%d.%d" line char
688
if {[string compare $tkPriv(prevPos) $i] != 0} {
689
set tkPriv(char) $char
691
set new [$w index [expr $line + $n].$tkPriv(char)]
692
if {[$w compare $new == end] || [$w compare $new == "insert linestart"]} {
695
set tkPriv(prevPos) $new
700
# Returns the index of the beginning of the paragraph just before a given
701
# position in the text (the beginning of a paragraph is the first non-blank
702
# character after a blank line).
705
# w - The text window in which the cursor is to move.
706
# pos - Position at which to start search.
708
proc tkTextPrevPara {w pos} {
709
set pos [$w index "$pos linestart"]
711
if {(([$w get "$pos - 1 line"] == "\n") && ([$w get $pos] != "\n"))
712
|| ($pos == "1.0")} {
713
if [regexp -indices {^[ ]+(.)} [$w get $pos "$pos lineend"] \
715
set pos [$w index "$pos + [lindex $index 0] chars"]
717
if {[$w compare $pos != insert] || ($pos == "1.0")} {
721
set pos [$w index "$pos - 1 line"]
726
# Returns the index of the beginning of the paragraph just after a given
727
# position in the text (the beginning of a paragraph is the first non-blank
728
# character after a blank line).
731
# w - The text window in which the cursor is to move.
732
# start - Position at which to start search.
734
proc tkTextNextPara {w start} {
735
set pos [$w index "$start linestart + 1 line"]
736
while {[$w get $pos] != "\n"} {
737
if [$w compare $pos == end] {
738
return [$w index "end - 1c"]
740
set pos [$w index "$pos + 1 line"]
742
while {[$w get $pos] == "\n"} {
743
set pos [$w index "$pos + 1 line"]
744
if [$w compare $pos == end] {
745
return [$w index "end - 1c"]
748
if [regexp -indices {^[ ]+(.)} [$w get $pos "$pos lineend"] \
750
return [$w index "$pos + [lindex $index 0] chars"]
755
# tkTextScrollPages --
756
# This is a utility procedure used in bindings for moving up and down
757
# pages and possibly extending the selection along the way. It scrolls
758
# the view in the widget by the number of pages, and it returns the
759
# index of the character that is at the same position in the new view
760
# as the insertion cursor used to be in the old view.
763
# w - The text window in which the cursor is to move.
764
# count - Number of pages forward to scroll; may be negative
765
# to scroll backwards.
767
proc tkTextScrollPages {w count} {
768
set bbox [$w bbox insert]
769
$w yview scroll $count pages
771
return [$w index @[expr [winfo height $w]/2],0]
773
return [$w index @[lindex $bbox 0],[lindex $bbox 1]]
777
# This procedure implements the "transpose" function for text widgets.
778
# It tranposes the characters on either side of the insertion cursor,
779
# unless the cursor is at the end of the line. In this case it
780
# transposes the two characters to the left of the cursor. In either
781
# case, the cursor ends up to the right of the transposed characters.
784
# w - Text window in which to transpose.
786
proc tkTextTranspose w {
788
if [$w compare $pos != "$pos lineend"] {
789
set pos [$w index "$pos + 1 char"]
791
set new [$w get "$pos - 1 char"][$w get "$pos - 2 char"]
792
if [$w compare "$pos - 1 char" == 1.0] {
795
$w delete "$pos - 2 char" $pos
796
$w insert insert $new