TIP 433: Add %M binding substitution

Bounty program for improvements to Tcl and certain Tcl packages.
Author:		Joe Mistachkin <[email protected]>
Author:		Brian Griffin <[email protected]>
Author:         Don Porter <[email protected]>
State:          Final
Type:           Project
Vote:           Done
Created:        25-Feb-2015
Tcl-Version:    8.6.4
Tk-Branch:      bindScriptCount


This TIP proposes one new binding substitution, %M, to access the number of script-based binding patterns matched so far for the event.


In a presentation at the 2012 Tcl Conference (http://www.tclcommunityassociation.org/wub/proceedings/Proceedings-2012/RonWold/Customizable-Keyboard-Shortcuts.pdf\), Ron Wold pointed out that coding global catch-all scripts bound to the same event in many widgets is complicated because Tk's bind machinery allows any bind script to use break to prevent later bind scripts from evaluating. Among a known set of bind scripts, this is a useful technique, but it interferes with the non-coordinated introduction of additional bind scripts on the same event.

An alternative strategy is to avoid any bind script using break, but to give the latter scripts the means to detect when an earlier script has run so it can defer its own operations. That motivates the introduction of a means by which a bind script can discover something about the history of other bind script evaluations on the same event.


Add to the set of substitutions made in scripts passed to bind the new one, %M. When the substring %M appears in a binding script, it will be replaced with a count of the number of binding script evaluations that have already been performed in the handling of the current event.

Simple Example

The script...

   pack [entry .e]
   bind all <Key> {set z %M}
   bind Entry <Key> {set y %M}
   bind .e <Key> {set x %M}
   event generate .e <Key-a>
   list $x $y $z

will produce the result:

   0 1 2

Use Case Example

One of the default bind scripts in Tk is

 event add <<NextWindow>> <Tab>
 bind all <<NextWindow>> {tk::TabToWindow [tk_focusNext %W]}

which permits a anywhere in Tk to shift the focus.

Some widgets have their own uses for , though, notably

 bind Text <Tab> {
     if {[%W cget -state] eq "normal"} {
         tk::TextInsert %W \t
         focus %W

where a text widget in normal state accepts Tabs as entered text like any other keypresses. The break in this script serves to prevent the focus shift that would otherwise take place. Since the same Tk developers coded both bind scripts, the global knowledge can make the system work as a whole.

However, a third party facility trying to join the party has difficulty. Consider a simple key logging facility,

 bind all <Key> {log_key %W %k %s %x %y %X %Y %A}

This will fail to log Tabs typed in a Text due to the break noted above.

With the %M binding proposed here, an alternative set of bind scripts is possible.

 bind all <<NextWindow>> {if {%M==0} {tk::TabToWindow [tk_focusNext %W]}}

 bind Text <Tab> {
         tk::TextInsert %W \t
         focus %W

In fact with the revised script bound to all it may be that the script bound to is no longer needed at all and the general default binding

 bind Text <KeyPress> {tk::TextInsert %W %A}

is sufficient.

With this alternative in place the third-party keylogger would work.


Any %M in an existing bind script will now stop reproducing itself literally, and will result in the new substitution. This has the potential to cause trouble with any bind scripts that themselves make use of clock scan or clock format or any other command that invites the use of the literal string %M. Dealing with such a situation is not difficult, but it is still a potential incompatibility.


This feature is already implemented and committed to both the core-8-5-branch and the trunk, and is poised to be released as part of Tk 8.5.18 and Tk 8.6.4. Any objections to that should be raised in TIP discussion and voting.


This document has been placed in the public domain.