Tk Source Code

View Ticket
Login
Ticket UUID: 631a0b2d95b8426a8851829b16a9d77b9f79b03e
Title: text tag bind of tag covering 1.0 to end stops working after clearing all text and entering new text
Type: Bug Version: 8.6.9.8609.2, core-8-6-branch, revised_text
Submitter: AndyMaleh Created on: 2021-11-10 00:12:11
Subsystem: 18. [text] Assigned To: fvogel
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2021-12-04 18:26:11
Resolution: Fixed Closed By: fvogel
    Closed on: 2021-12-04 18:26:11
Description:
I wrote code that monitored key presses upon moving around a text widget with the arrow keys. It basically registers a listener to <KeyPress> event on a pre-added tag called 'all' that covers a range of index 1.0 to end.

Here is a code example of this written in Ruby:

```ruby
require 'tk'

@root = Tk::Root.new

@text = Tk::Text.new(@root).grid
@text.value = "Some giberish repeated many times\n"*20
@text.tag_add('all', '1.0', 'end')
@text.tag_bind('all', 'KeyPress', ->(event) {puts 'key press', event.inspect})

@root.mainloop
```

You can navigate around the Tk::Text widget using the arrow keys with no problem. It always fires the <KeyPress> listener anywhere in the 1.0 to end range, printing the event information.

Trouble starts once I clear all the text by highlighting with CMD+A and then hitting DELETE. Afterwards, when I type new text and then move the arrow keys around, no firing of the <KeyPress> event happens anymore.

However, if I insert a number of new lines, it sometimes starts firing again, albeit temporarily, and then it stops again.

The tag bind functionality seems broken for clearing text and re-entering new text when the tag covers '1.0' to 'end'.

I recreated the same issue in Python and Tcl, so I know it is not a Ruby-only issue.

Here is similar Python code that also has the same issue:

```python
from tkinter import *
from tkinter import ttk

root = Tk()

text = Text(root)
text.grid()
text.insert('1.0', "Some giberish\nMore giberish\nNot well spelled giberish")
text.tag_add('all', '1.0', 'end')
def print_info(event):
  print('key press')
  print(event)
  
text.tag_bind('all', '<KeyPress>', print_info)

root.mainloop()
```

Here is a Tcl version that tries to rebind the tag covering '1.0' to 'end' on every text modification (but fails to address the issue):

```tcl
text .t
pack .t
.t insert end "Some giberish\nMore giberish\nNot well spelled giberish"
.t tag add all 0.0 end
.t tag configure all -background yellow

proc key {args} {
puts stderr "keypress args='$args'"
}

proc changed {args} {
puts stderr "modified"
.t edit modified 0
.t tag add all 0.0 end
}

bind .t <<Modified>> [list changed]
.t tag bind all <KeyPress> [list key %K]
.t edit modified 0
```

I originally reported the issue at the Ruby Tk project:
https://github.com/ruby/tk/issues/40

Next, I reported at the ActiveTcl project:
https://community.activestate.com/t/tk-text-tag-bind-issue-with-activetcl-8-6-occurs-when-using-tk-in-both-ruby-and-python/7665

Afterwards, I reported at the Tcl google group (someone there confirmed encountering the issue):
https://groups.google.com/g/comp.lang.tcl/c/jdojADKDvYU

I do have a workaround for now, which is to simply bind the global '<KeyPress>' event on text directly instead of using a tag covering 1.0 to end.

Still, I thought I'd report anyways since it might be beneficial to sort out any bugs relating to it.

Best,

Andy Maleh

p.s. I am using ActiveTcl-8.6.9.8609.2-macosx10.9-x86_64-93b04018
User Comments: fvogel added on 2021-12-04 18:26:11:
Merged into core-8-6-branch, trunk and revised_text.

fvogel added on 2021-11-14 14:07:49:

I have crafted the fix for the revised text widget as well, see bug-631a0b2d95_revised_text.


fvogel added on 2021-11-13 15:50:45:

Digging further, the current behavior was present since the very beginning. It was later refined in [0e36a93a] when fixing [220780fff].

Since people in comp.lang.tcl request that this behavior should be improved, please see branch bug-631a0b2d95 for my proposal.


fvogel added on 2021-11-12 21:38:35:

After analysis in the code and documentation, and as unexpected as it seems, this is working as designed. From the 'text' man page: "Event bindings for a text widget use the 'current' mark". And "the mark 'current' is associated with the character closest to the mouse and is adjusted automatically to track the mouse position and any changes to the text in the widget[...]".

This means in particular that when the mouse leaves the text widget the 'current' mark is no longer updated (specifically in the code the number of tags to consider for event matching is set to zero: numNewTags = 0 in TkTextPickCurrent()). This is also true when the mouse is in the border of the widget (including the area at the bottom of the last line). When the 'current' mark does not change, the tag binding does not fire.

When the mouse hovers a new character in the text, TkTextBindProc() and TkTextPickCurrent() arrange to pick a new 'current' mark: the 'current' mark changes (the number of tag bindings to consider for event matching becomes the number of tags at the new 'current' position). The tag binding fires because of this (and because the associated event matches the binding sequence).

Try it with your script. See the difference when the mouse is inside the text (<KeyPress> fires) and when the mouse is out of the widget (<KeyPress> does not fire). This dependency on the mouse position must be why you thought that "if I insert a number of new lines, it sometimes starts firing again, albeit temporarily, and then it stops again".

Here is a shorter example:

package require Tk
proc key {args} {
  puts "keypress args='$args'"
}
pack [text .t]
.t insert end "Line 1"
.t tag add tag1 1.0 end
.t tag configure tag1 -background yellow
.t tag bind tag1 <KeyPress> {key %K}

.t mark set insert 1.2
focus -force .t
# now press a key
#  --> <KeyPress> does not fire if the mouse is out of the widget or in its border
#  --> <KeyPress> fires otherwise, i.e. when the mouse is in the tagged (yellow) area

That now being explained, how could it be made clearer in the man page?


fvogel added on 2021-11-10 21:05:18:

Shouldn't be due to TIP #532 since this TIP went in 8.6.10 only (commit for TIP #532 is [84fbb57a] dated 2019-08-28 while 8.6.9 got released on 2018-11-16).


fvogel added on 2021-11-10 20:29:50:
Also with revised_text.

fvogel added on 2021-11-10 12:37:07:
Correction. I also happens with tclsh. I had not noticed it so far, but the insertion of more than a single line makes it work again ?!? This will certainly be a fun thing to debug.

fvogel added on 2021-11-10 07:34:11:
I can reproduce this in wish, but not with tclsh.

I suspect an interaction with the wish console, which is based on a text widget.