Auxiliary Library for TK Kevin B. Kenny GE Corporate R&D kennykb@crd.ge.com ======================================================================== Copyright 1993, 1994 by General Electric Company | All rights reserved. Published only in limited copyright sense. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of General Electric not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. General Electric makes no representations about the suitability of this software for any purpose. It is provided ``as is'' without express or implied warranty. This work was supported in part by the DARPA Initiative in Concurrent Engineering (DICE) through DARPA Contracts MDA972-88-C-0047 and MDA972-92-C-0027. This work was supported in part by the Tri-Services Microwave and Millimeter-Wave Advanced Computational Environment (MMACE) program under Naval Research Laboratory contract N00014-92-C-2044. Changes to this document since 1 November 1993 are indicated with | vertical lines in the right margin. Changes to the source code since | that date are listed in the `Changes' file. | RCS information for this document: $Id: README,v 1.2 1994/10/27 18:29:42 kennykb Exp $ $Source: /tmp_mnt/projects/cliff/iam/all/src/tkauxlib/RCS/README,v $ ======================================================================== PREFACE There has been a flurry of discussion on the comp.lang.tcl newsgroup recently about extending the keyboard and mouse bindings for such things as tab traversal, keyboard operation of buttons, highlighting of the focused widget (as MOTIF does), arranging for keystrokes to replace the selection in entry boxes, and other similar look and feel issues. I've been working on resolving these issues in Tk for some time now, and I've put together an auxiliary library, which is designed to operate in parallel with /usr/local/lib/tk, that addresses some of them. I've been enough of a perfectionist, however, that I've been reluctant to release it. While the auxiliary library isn't really ready for prime time, the level of interest generated by the newsgroup discussions shows that there is a widespread need, and has convinced me to package a beta release, so that other people can experiment with the concepts. While it will undoubtedly be difficult to achieve consensus among the Tk community about the best way to resolve many of these issues, having a reference implementation may help serve as a straw man to start bringing the ideas together. I'm putting the library code in a file called, `tkauxlib.tar.Z' in the incoming directory on harbor.ecn.purdue.edu, from whence it should migrate to the download directories. I'll look for it in a couple of days and follow-up once it's in place. BACKGROUND: THE MODEL-VIEW-CONTROLLER PARADIGM One popular way to structure user interfaces is the model-view- controller paradigm used in Smalltalk. In this scheme, any user interface is divided into three nearly independent components: * the model, which holds the data being manipulated and conducts all the computations, * the view, which displays the data to the user, and * the controller, which responds to all user actions and notifies the model and view appropriately. The model-view-controller scheme is quite versatile, since it allows for: * multiple, simultaneous views of the same data (as a trivial example, the same number could simultaneously appear on a scale, in an entry box, and as a co-ordinate of something on a canvas), * consistency of the user interface -- all widgets that share the same controller respond to user input in precisely the same way. The model-view-controller paradigm is an obvious choice for user interface design in Tk. The models, views, and controllers all have obvious, natural representations in Tcl, and the event management does all the really difficult tasks of co-ordinating them. The natural representation of a model is as a cluster of Tcl variables, together with procedures and commands that manipulate them. The `trace' mechanism in Tcl, and the corresponding `Tcl_TraceVar' mechanism in the C library, provide an elegant way for a model to notify a view when data change and must be redisplayed. In fact, the built-in widgets of Tcl, for the most part, use such a mechanism -- all of the widgets that support `-variable' or `-textvariable' configuration switches arrange to redisplay themselves when the underlying variables change, using one of the trace mechanisms. The natural representation of a view, of course, is as a set of Tk widgets. Generally speaking, they will be grouped in a single subtree of the widget tree, particularly as descendants of a frame or toplevel widget. The reason for using a frame or toplevel, as opposed to any other sort of widget such as a canvas, is the fact that the widget class may be specified using a `-class' configuration option, and therefore a particular class of view can have its own set of resources controlling its appearance. The controller is defined by the set of event bindings applied to the view using the Tk `bind' command. In the present implementation of Tk, the interaction among controllers can get complicated because multiple bindings cannot be established for a single event -- a Tcl-level event dispatcher is therefore needed to sort things out. This situation should be greatly alleviated in Tk 4.0, where the `bind tags' mechanism should allow greater flexibility in dispatching events. The problems of look and feel that the posters have been discussing are all issues related to the controller. The Tk widgets do a reasonable job at providing the views, and the models are perforce specific to the applications. CONTROLLER ISSUES Tk's default bindings already address a number of the requirements for a shared controller. Most of the mouse functions are consistent with the Motif style. Keyboard bindings (at least rudimentary ones) are provided for text and entry widgets. Tk is tantalizing close to giving a comparatively unskilled programmer full Motif compliance automatically. Unfortunately, the remaining functions are not always that easy to provide. There are some complex requirements that remain to be addressed: * Non-text-based widgets cannot accept the keyboard focus. Buttons, scales, listboxes, and so on cannot be driven from the keyboard, and require the use of a mouse. * There is no concept of tab traversal. The Tab and Shift-Tab keys (or Control-Tab and Control-Shift-Tab, if Tab and Shift-Tab have a natural meaning) must be bound explicitly for every component of the user interface if they are to be used to navigate. This explicit binding imposes a fair amount of bookkeeping on the programmer. * There is no convenient way to highlight the widget that has the keyboard focus. * Confining the keyboard focus in support of modal dialogs, auxiliary toplevel windows, and so on can require some tricky coding. The auxiliary library begins to handle some of these needs, by establishing another collection of bindings. The remainder of this paper discusses the functions that the auxiliary library provides, and how to change their behavior. STARTING THE CONTROLLER A Tk application that wishes to use the enhanced controller puts it in place by executing the statement: source /usr/local/lib/tkaux/init.tcl This initialization file loads the rest of the controller. * It puts the directory where it resides (normally /usr/local/lib/tkaux, but obtained from `info script' so that the library can reside anywhere) at the head of `auto-path'. Most of the functions in the controller are auto-loaded on demand, and some of them override corresponding ones in `$tk_library', so the auxiliary library must preceded $tk_library on the load path. * It loads a new `tkerror' function, overriding the system default. The primary enhancement to `tkerror' is the ability to save a dump of the Tk workspace to a file for analysis, enabling postmortem debugging of Tk scripts run at remote sites. * It established class bindings to support keyboard actions in a variety of widgets. * It establishes and bindings for the application main window and the Toplevel window class. These bindings support a mixed focus model where change of focus among top-level windows is handles by the window manager, while focus is explicit within top-level windows. Moreover, each top-level window remembers its own focus, allowing them to appear to the user as independent applications. The controller at present allows only explicit focus within composite widgets. While it would be possible to add a focus-follows-mouse model, I personally would find the behavior maddening; I dislike having to keep the mouse pointer positioned precisely when I type, and focus-follows-mouse would require a warp-cursor function for tab traversal to operate correctly. * It establishes `all' bindings for Destroy and UnMap events. These bindings allow for multiple widget destructors, giving various components a chance to clean up after themselves when a widget is destroyed. This capability allows a variety of the controller's procedures to use temporary variables without risking memory leaks. The bindings also allow focus to be redirected when a window leaves the screen. MENU TRAVERSAL The tk_bindForTraversal procedure in the Tk library does menu traversal (based on the F10 key) and allows keyboard accelerators (based on the Alt key). The auxiliary library's controller includes these functions without modification, and adds the corresponding bindings to all widgets that accept the keyboard focus. TAB TRAVERSAL The focus_bindForTraversal procedure in the auxiliary library established Tab traversal binding, and the initialization code invokes it for all widgets that accept the keyboard focus. Tab traversal allows keyboard-based navigation among the widgets. The following keyboard actions are supported: Table 1. Keyboard Actions for Tab Traversal Key Action ------------------------------------------------------------ Tab Direct the focus to the next application component. Shift-Tab Direct the focus to the previous application component. Linefeed Return KP_Enter Control-j Control-m Invoke the current `default button.' Widgets such as texts, in which keys such as Tab and Return have a | natural meaning, can use an optional parameter, `-controlonly', to specify that these actions apply only when the Control key is also depressed; change of focus will then take place only when the user presses Control-Tab, Control-Shift-Tab, Control-Linefeed, and so on. Otherwise, the keys are accepted either with or without the Control key. The problem of defining the `next' and `previous' application component is not straightforward. These must identify the next or previous object that accepts the keyboard focus. The object my be either a widget or a canvas item. In addition, there must be a well-defined order among the components. The most obvious answer is to order the components geometrically, perhaps top-to-bottom and left-to-right. Unfortunately, such a sequence has two problems, First, it is too inflexible; for instance, it gives the programmer no way of specifying that a set of widgets is arranged in columns instead of rows. Second, the many varieties of geometry management in Tk (packing, placement, and the `create window' operation in canvases, with more varieties expected) make it virtually impossible to determine the geometric sequence of the widgets in a reliable manner. Instead, the auxiliary library resorts to defining an arbitrary sequence. * The application root window is the first widget in the sequence. * Child widgets follow their parents, in stacking order. (Those who read the library code will note that there is version-dependent code throughout focusAux.tcl, since [winfo children] returns the child widgets in reverse stacking order in Tk 3.2 and earlier releases.) * Canvas items follow the canvas, in order of creation. They precede any child widgets of the canvas. Note that a `window' canvas item does not take part in the sequence, since there is no way for it to take the keyboard focus -- events are routed instead to the corresponding widget. In addition, the keyboard focus is directed only to those objects that can use it: * A widget receives the keyboard focus only if it handles at least one KeyPress or KeyRelease event, that is, only if a `bind' command for a KeyPress or KeyRelease event has been executed against that widget or its class. Bindings for all widgets are disregarded in this test, since otherwise every widget would appear to accept the focus, owing to the binding of the F10 key. A widget that supports the `-state' configuration option does not receive the keyboard focus if its state is `disabled'. * A canvas item receives the keyboard focus only if it handles at least one KeyPress or KeyRelease event, that is, only if the application has executed a `bind' widget command for a KeyPress or KeyRelease event and specified that canvas item or one of its tags. Once again, `all' bindings are disregarded, more for the sake of consistency with widgets than for any sound technical reason. * No widget receives the focus if it is not mapped. If a widget is unmapped (for a reason other than withdrawing its top-level window) while it owns the focus, focus is automatically redirected to the previous focusable object in the application. If a widget is destroyed while it owns the focus, focus is also redirected. This behavior can be modified by using the focus_skip command. The user can specify that a widget should never receive the keyboard focus, or that it should receive the keyboard focus even if its state is `disabled'. The list of objects that receive focus is circular. If the user tabs out of the end of the application, focus returns at the beginning, and contrarily, if the user Shift-tabs out of the beginning, focus returns at the end. This behavior may be made more local by using the `focus_confine' procedure, which keeps the Tab and Shift-Tab keys from directing focus beyond a specified subtree of the widget hierarchy. By default, any top-level window confines the focus within its substructure: the user cannot use the Tab key to move to another top-level window. BUTTONS, RADIOBUTTONS, AND CHECKBUTTONS The controller contains code that provides for keyboard focus to buttons, checkbuttons, and radiobuttons. The buttons handle the following keys: Table 2. Key bindings for buttons. Key Function ------------------------------------------------------------ space Return KP_Enter Invoke the button. Tab Direct focus to the next object. Shift-Tab Direct focus to the previous object. F10 Alt Control menu traversal. In addition, for each top-level window, the controller maintains a record of the `default' button. This is understood to be the last button invoked from the keyboard, rather than using the mouse. The Return and KP_Enter keys are bound in most widgets so that they invoke the default button. In text widgets, where these keys have a natural meaning, they are replaced by Control-Return and Control-KP_Enter. The default button, and the button that owns the keyboard focus, need to be highlighted. Since Tk does not provide this function with simple button widgets, the library includes a `focusable' command that automatically creates a button within a frame so that the frame can be highlighted. Thus, instead of saying: button .foo.bar -text Quit -command "after 1 exit" to create a button, most code that uses the auxiliary library will create buttons with focusable button .foo.bar \ -text Quit -command "after 1 exit" This operation will create a frame `.foo.bar', with a button `.foo.bar.b' packed within it. The frame will normally be flat, and have the same background color as the button. Making the button default will cause the frame to be sunken, and directing keyboard focus to the button will cause the frame to take on the button's active background color. When a new top-level window is created, its default will not be highlighted automatically. It may be highlighted, and even changed, by the command: button_setDefault pathName where pathName is the name of the button or one of its ancestors (If there are multiple buttons within pathName's widget tree, the first will be selected). CANVASES The controller does not establish any particular set of bindings for canvases or their items. The user, however, may choose to do so. A common set of bindings that provided for tab traversal and keyboard traversal of menus can be obtained by invoking canvas_bindForTraversal pathName tagOrId ?-controlonly? where pathName is the path name of a canvas tagOrID is a tag or item ID that specifies the canvas item(s) for which the bindings should be established. -controlonly is a flag that, if present, requires the Control key with Tab, Shift-Tab, Return, and KP_Enter, allowing them to have natural meanings. This behavior is useful with text items, since the user may want to enter tab and newline characters into the text. ENTRIES The binding set for entry widgets is perhaps the most developed of all the bindings. It supports a number of keyboard operations: * Plain printing characters replace the selection, instead of being inserted at the insertion point, if the insertion point is within the selection. This change supports the Motif-style action of stroking out an area of text with the mouse and typing over it. * The Insert key works Motif-fashion. It changes the appearance of the insertion cursor and toggles `replace mode'. In replace mode, typing a plain printing character repaces the character to the right of the cursor, rather than inserting before it. * The Home, Begin, and Control-A keys position the insertion point at the left side of the entry box. * The Left arrow and Control-B keys move the insertion point one position to the left. * The Control-C key causes an error with the message `Interrupt', which may be caught and processed elsewhere in the application. * The DeleteRight and Control-D keys delete the character to the right of the insertion point. * The End and Control-E keys move the insertion point to the end of the entry. * The Right arrow and Control-F keys move the insertion point one position to the right. * The BackSpace and Control-H keys delete the character to the left of the insertion point. * The Control-K key erases from the insertion point to the end of the entry. (On keyboards that support Erase-EOL, this would be a natural binding for the latter key.) * The Control-L key centers the insertion point within the visible part of the field. * The Tab, Shift-Tab Return, KP_Enter, LineFeed, Control-j and Control-m keys are bound as described in the preceding sections. * The Control-U key clears the entry. (On keyboards that have a Clear Screen key, this would be a natural binding for it). * The Control-W key clears the selection. (Only Emacs users probably care about this one.) * The Control-Y key pastes the selection into the entry at the insertion cursor. * The Insert key enters or leaves `replace mode', as described above. * The Delete key clears the selection if there is one, and otherwise erases the character to the left of the insertion cursor. * The Escape and Break keys undo the effect of typing in the entry and revert it to the contents of its text variable. In order for the Escape key to operate, code also is added to handle focusing and defocusing an entry variable. When an entry has the keyboard focus, it is temporarily disconnected from its text variable. When it loses the focus, the text variable is updated from the content of the entry. At this time, the content may be validated; the `INPUT VALIDATION' section below discusses entry validation. SCALES The controller allows keyboard focus to scale widgets. When a scale widget is focused, its foreground color changes to the active foreground color. Scale widgets can accept the following keys: Table 3. Keyboard Bindings for Scale Widgets Key Function ------------------------------------------------------------ Up The arrow keys push the scale in the Down direction of the arrow. The orientation Left of the scale is taken into account; if Right the -from value is greater than the -to value, the arrow keys still move the scale geometrically. plus The plus and minus sign increment and minus decrement the scale's value, irrespective of its orientation Tab Keyboard traversal is provided as with all Shift-Tab the other widgets. Return Linefeed KP_Enter F10 Alt TEXTS Bindings are added to text widgets to support tab traversal and default buttons. The relevant keys (Return, Linefeed, KP_Enter, Tab, and Shift-Tab) are recognized only in conjunction with the Control key. No other bindings beyond the Tk library ones are provided for texts. An Emacs-like binding set would be a worthwhile addition; the fine one that Andrew Payne has provided would be a good place to start. I would, however, like a way to override the bindings in the application default file, and doing a parser for the Motif style of specifying keyboard bindings looks to be a fair amount of work. TOP-LEVEL WINDOWS The concept of the default button and the current keyboard focus is maintained independently for each top-level window, making them appear to the user almost like separate applications (The alternative would be to allow TAB traversal between them, which requires warping the mouse cursor as well if a consistent look and feel is to be provided). Bindings are established for FocusIn and FocusOut events on top-level windows to switch to the currently focused widget when the window manager directs focus to a toplevel window. EVENT MANAGEMENT Several places in the controller require somewhat more sophisticated event management than Tk currently provides, although somewhat less than the proposed `bind tags' mechanism. For this reason, certain events are trapped by library code and dispatched to multiple handlers. These include both window system events and new events defined by the controller. They are Table 4. Dispatched Events Event Meaning ------------------------------------------------------------ Destroy A widget has been destroyed. GainFocus A widget is receiving the keyboard focus. LoseFocus A widget is losing the keyboard focus. These events are more restrictive than . Redirecting the focus among top level windows with the window manager does not cause GainFocus and LoseFocus events; only Tab traversal and mouse events do so. Unmap A widget is being unmapped. UpdateContent A widget is requested to bring its content into synchronization with its text variable. Validate A widget is requested to check that its content passes any validation tests. The UpdateContent and Validate events are discussed further under `INPUT VALIDATION' below. All of these events are processed by the `widget_bind', `widget_addBinding', and `widget_event' procedures in `widget.tcl'. INPUT VALIDATION The controller supports deferred update to entry boxes; generally speaking, an entry box's content is not reflected in its text variable until the entry loses the focus. At that time, the Validate event will be signalled, requesting that the content be checked for consistency. Two validation procedures are supplied by the auxiliary library: checkInt and checkReal. The checkInt procedure checks that the entry contains an integer, and checkReal checks that it contains a real number. Either of these can be connected to an entry widget by executing: widget_addBinding .foo.entry Validate "checkReal .foo.entry" or widget_addBinding .foo.entry Validate "checkInteger .foo.entry" In addition, the programmer can provide other validation procedures. They should accept the window path name as a parameter, return normally if the content passes the test, and return an error if the content fails the test. Since the actual content of an entry box's text variable is not updated until it has been validated, commands that are invoked from buttons and menus may need to force the current focus item to be synchronized in order to have a complete set of current data. The command focus_update performs this synchronization, does all necessary validation, and returns 0 if it was successful and 1 if it failed. Thus, many procedures that handle buttons begin with the code: if [focus_update] return VIEWS In addition to the controller, the auxiliary library provides a collection of views that appear to be generally useful. They include: choicebox: A dialog box with a message, an icon, and a set of buttons representing possible actions. errormessage: A choicebox adapted to displaying an error message, with a single `Dismiss' button. fileselect: A reasonably complete file selection dialog. fraction: A canvas representing a fraction of something, for example, the part of a task that has been completed, by a bar partially filling the canvas. icon: A label containing a bitmap that is found by looking for a `.xbm' file along the auto_path. The auxiliary procedure, `icon_find', is also provided to look up the bitmap without creating a widget, so that the bitmap can be installed in a button, canvas item, or other similar place. A number of standard icons come with the library, including: bomb pageup error rtfm filltriangled think filltriangler triangled pagedown triangler (Some of these icons are supplied for tk 3.2, and are built in in tk 3.3.) labeledentry: A combination widget that has an entry box with optional labels to its left and right. It is handly in combining forms with lines that look like: Name ______________________ Age _______________ years password: | A variant on the `entry' widget that does not display the | content of the entry box to the user. It is intended for | entering passwords and similar strings that must not | be displayed. | progress: A combination widget comprising a message, a fraction, and a `quit" button. It is used for indicating the progress of long-running operations. VIEW MODIFIERS Several procedures are available to modify the appearance and behavior of widgets by packing them into frames and establishing various bindings. The `focusable' one was discussed under `BUTTONS' above; it provides Motif-style highlighting for buttons. Other view modifiers include: collapsible: Makes a widget that is packed only upon request. When the widget is unpacked (`collapsed'), it is represented by a triangle and a title: > My widget When the triangle (which is a button) is invoked, it rotates, and the actual widget appears below it: V My widget .-----------------. | | | | `-----------------' Judicious use of collapsible widgets can often `neaten' the appearance of a user interface. modalDialog: The modalDialog procedure makes a widget a modal dialog. The widget grabs the keyboard and mouse, and handles all interaction until it is dismissed (through `modalDialog_end'). transient: The transient procedure packs the widget into a top-level window, marks it transient, groups it appropriately, and centers it relative to another top-level window. Most transient dialog boxes come through `transient' to do their window manager interactions. MISCELLANEOUS SUPPORT PROCEDURES The library contains a number of service procedures that support it internally, and are also available to the user. They include: debug_traceProc: | Trace entry to and exit from a given Tcl procedure or command. | debug_traces: Find out what traces are active on a variable or set of variables. debug_dump: Dump the Tcl workspace to a file for post-mortem analysis (This ersatz `core dump' gives a way of analyzing application crashes at remote sites). gensym: Produce a unique generated symbol. int: A `greatest integer' function that is compatible with either Extended Tcl or Tcl 7.0. msort: A sort with user key comparison. This procedure is largely obsoleted by the new version of `lsort' in Tcl 7.0; the `merge' procedure may still be useful. nop: A `no-operation' procedure. The commonest need for this is to configure a `scale' widget -- the `-command' option on scales is executed even if the command is the null string. require: A procedure that forces another procedure to be autoloaded without the risk of invoking the `unknown' procedure recursively. set?: Set a variable if the new value is different from the old one. This procedure avoids triggering traces unnecessarily -- some trace procedures can be expensive, if tracing is used for constraint propagation. static: Define a static variable within a Tcl procedure (This procedure appears courtest of Karl Lehenbauer). trace_action: Import a traced variable into the trace handler, and avoid `access denied by trace command' errors in Tcl 6.7 and earlier releases. widget_waitVariable: Perform the same action as `tkwait variable' but avoid hanging the process if the application is destroyed before the variable is set. (Note that this results in returning control into the destroyed application, and any Tk command will cause a core dump!) widget_checkAndDestroy: Destroys a widget if and only if it still exists. This procedure allows the handler on a child widget to destroy the parent widget as well. winlabel: Return the symbolic name of a window for use in error messages. EXPERIMENTAL PROCEDURES There is an experimental set of procedures, accessed via `composite_define', that allow the user to define composite widgets that | function as first-class widgets in that they accept widget commands, have their own configuration flags, and do on. These procedures are currently far too inefficient to be useful, owing to the way that they propagate configuration around the widget tree. Improving these procedures is likely to be my next general-purpose Tk project. BUGS Entry widgets do not handle double or triple mouse clicks. Listboxes do not yet support keyboard bindings. There is no way to configure the file selection dialog to select a directory instead of a file. There is no way to control whether the file selection dialog exports the selection. The file selection dialog needs a `Rescan' function to rescan the current directory. The `winlabel' procedure needs to be generalized. The `fraction' and `progress' widgets can be made much more efficient by batching updates. Eventually, all composite widgets should use `widget_define' to allow user reconfiguration.