From AlphaWiki

Development: [dialog] command callbacks

This page is meant for discussing the dialog interface provided to AlphaTcl by the Alpha executable (and Alphatk). A lot of this is about callbacks from the dialog command.

The dialog command in Alpha 7

The dialog command in Alpha 7 opens a dialog window whose contents are specified by the (usually rather long list of) options to the command. Most options add precisely one atom to the dialog: these are pretty much the same things as the dialog items one finds in DITL resources in normal MacOS programs, but many higher level dialog commands in AlphaTcl work with 'items' that are composed from two or more atoms and therefore 'atom' was chosen to make the terminology unambiguous. A control is an atom that in some way can be directly affected by the user; these includes push-buttons, checkboxes, editable text boxes, and pop-up menus. The most common type of atom that isn't a control is a box containing static text.

For most controls the basic type fully defines the reaction to any event (typing, clicking the mouse, etc.) generated by the user and thus there is no need for the AlphaTcl programmer to specify the type, the title and value, and the screen rectangle for the atom. Once this is given, the GUI can handle the rest, and when the dialog window is closed then the current values of all controls in it are returned in the result of the dialog command. The main exception to this pattern is the push-buttons. What the GUI knows about push-buttons is what it takes to push them and maybe how they should be highlighted when the user does this, but that is about the extent of it. Pushing a button usually tells the program to do something, but the GUI has no way of knowing what this action should be.

Alpha 7 handles push-buttons in dialogs by immediately returning from the dialog command when one is pushed. This set-up has the advantage that Alpha doesn't have to know much at all about the dialog. All low-level things are handled in the Toolbox and all high-level things are handled in AlphaTcl. The disadvantage is that whenever the user wants to do a high-level action, the dialog command must return so that AlphaTcl can gain control and that means the dialog window must first be closed. Even for actions that would only change one atom in the entire dialog, Alpha 7 offers no other way of achieving this than to rebuild the entire dialog from scratch. Not only is this time-consuming and inefficient, it is also gives the user interface a rather disturbing appearance with dialog windows flashing in and out of existence.

Callbacks

The obvious alternative to having the dialog command return whenever it encounters a situation the the GUI cannot fully handle would be to provide it with a callback. This is a common techinque -- many Tcl and Tk commands use it in some form -- and thus it is hardly surprising that this is precisely what Alpha 8 and Alphatk does for push-buttons. In their 'dialog commands the -b option that defines a button can take a -set suboption which provides a callback action for that button. This callback is evaluated whenever the button is pushed. Alphatk furthermore uses callbacks to handle some high level aspects of drag-and-drop.

A matter that should be discussed is however what callback mechanism should be used, since even the standard commands of Tcl and Tk employ a variety of callback mechanisms. Most of them sooner or later use the eval command (or a C equivalent) to evaluate a piece of Tcl code that carries out what has to be done, but there is a considerable variation in the precise details of this. The simplest callback mechanism is of course to have a script that is evaluated, but the problem with this is that callbacks often need to get some input data about the particular situation they are called for. The fileevent command uses a callback that doesn't get any input data, and hence it is natural to this direct script mechanism.

The command prefix mechanism is used by for example the trace command. In this case the callback is a partial command, to which is appended one or several arguments (which contain data for the callback to operate on) just before the command is evaluated. This kind of callback is clean (in a functional programming way), have no syntax gotcha's, and the namespace code command gives explicit support for it. There are however some disadvantages. To begin with the command will almost always be a call to a Tcl procedure which is defined exclusively for this purpose, as that is the only way to parse the input data arguments. Because of this one usually ends up defining one procedure for each place the callback is used, which can in some cases be quite a niusanse, even though in others it is simply good programming style. Furthermore the mechanism tends to lend a certain rigidity to the interface, since there is very little room for changes to the set of input data that is provided. Each callback procedure must explicitly recieve all arguments provided to it, regardless of whether they are of interest or not.

The approach used by the bind Tk command for solving the input data problem might instead be called script with substitutions. Here the callback is again a script, but before the script is evaluated it is given a treatment similar to that of the formatString argument of format. This method works fine for nice values, but surprises are not uncommon if the data contains for example spaces. Another problem is the matter of how such a substitution should be implemented, but a combination of string map and format would probably suffice.

A callback mechanism which to the programmer looks like a close relative of script with substitutions is script with context. This is used for example by the scanmatch TclX command. The idea is that the callback is simply a (direct) script, but that script is evaluated in a special context where additional pieces of information are made available in variables. This avoids both the quoting problems of scripts with substitutions and the rigidity of command prefixes, hence it is the method I recommend using.

Implementing a script with context callback for Alphatk or emulating it in Alpha7 is about as easy as it could be. If all values are available in variables with the expected names, then the context is in places and calling the callback is simply a matter of saying

 
 eval $callback

(or perhaps

 
 catch $callback res

is better since it protects against errors in callback scripts). In Alpha8/AlphaX the matter is slightly more complicated, since there is AFAIK no way of creating a Tcl local context in C; local contexts are only created when Tcl procedures are called. So let's do that. If we just provide one Tcl procedure

 
 proc callback_central {script variables values} {
    foreach $variables $values {break}
    eval $script
 }

then every callback to a "script with context" can, on the C side, be implemented as a call to this procedure. One must supply it with the actual callback script $script, the list $variables of the names of the variables that constitute the context provided for the script, and the list of values that must be supplied for these variables, but the difference on the caller side to a command prefix callback is marginal.

This method of routing callbacks through a designated procedure may be useful in Alphatk as well, since it shields the local variables of the caller from unwanted modification by the callback script. It also makes it possible to do a slick initialisation of the context by making the relevant variables the arguments of the procedure. For example, if the information a drag-and-drop callback needs are dialog, item, and data, then with the procedure definition

 
 proc call_DnD_callback {script dialog item data} {
     eval $script
 }

one can call the callback as easy as

 
 call_DnD_callback $callback $dialog $item $data

and the callback script can access the information it needs as $dialog, $item, or $data.

But isn't that the same thing as command prefix? It sure looks similar to e.g.

 
 eval $callback \$dialog \$item \$data

but there are important differences. One is that with the context callback, one typically makes at most one procedure per type of callback, whereas with a command prefix callback it rather tends to be one procedure per piece of code you might want to call back to. In these circumstances, there are usually many more callees than callers. Another difference is that if it turns out that some additional piece of information is needed for the callback to be fully functional, then this is easy to provide with context callbacks: just change the above definition to

 
 proc call_DnD_callback {script dialog item data additional} {
     eval $script
 }

and of course the call of it to

 
 call_DnD_callback $callback $dialog $item $data $extra

This does not raise a compatibility problem, since both pieces of code would be part of the same software component (the Alphatk core). Since the context has merely been augmented, none of the callback scripts that already did work need to be changed. By contrast, if a command prefix callback mechanism had been used then all old callback callees will have to be changed, since their argument structures no longer matches that of the call. Similarly, if it turns out that some piece of information that is provided is not necessary since no callback callee uses it then the corresponding variable can simply be dropped if a script with context mechanism is used, but with a command prefix mechanism, dropping an argument will break all old callees.

Conclusion

The callback mechanisms for dialog and similar commands should look as follows. The user (AlphaTcl programmer) sets up a callback (activates some dialog atom so that there is a callback for it) by adding to the option for that atom a suitable suboption. This suboption should take the following information:

Of course, in cases where more than one callback is used (such as for drag-and-drop) then it is appropriate to handle this by providing two scripts that share the same "private" value.

Thus instead of specifying a Set button with callback as

 
 -b "Set${dialog::ellipsis}" -set [list\
    [list dialog::specialSet::file $dial "$page,$name"]\
    +1]\
 $left $top $right $bottom

-- which relies on that the callback is primarily made to dialog::itemSet, whose primary task besides evaluating the user-supplied callback command is to make dialog::modified implicitly update what is shown in the dialog, through some trickery with hooks -- one might do it as

 
 -b "Set${dialog::ellipsis}" -newset {
    foreach {dial var} $prvt {break}
    dialog::specialSet::file $dial $var
    dialog::setControlValue [lindex $atomList 0] [dialog::valGet $dial $var]
 } [list $dial "$page,$name"] {+1} $left $top $right $bottom

in which case the call to dialog::setControlValue (that actually updates what is shown in the dialog) is explicit rather than some implicit magic. All one needs for this is that the context where the callback script is evaluated is initialised so that $prvt is the private data and $atomList is the list of the identifiers of atoms that this callback wanted to be able to modify.

Lars Hellström


Category.Development

Retrieved from http://alphatcl.sourceforge.net/wiki/pmwiki.php/Development/DialogCommandCallbacks
Page last modified on November 16, 2007, at 05:42 PM