From: nyh@gauss.technion.ac.il (Nadav Har'El)
Newsgroups: comp.lang.tcl
Subject: Re: Keyboard focus and keyboard driven traversal
Date: 29 Jul 92 10:57:05 GMT
Organization: Technion, Israel Institute of Technology

In article <1992Jul28.214959.19452@twg.com>, "David Herron" <david@twg.com> writes:
> Greetings!
> 
> One of the fundamental things in Motif is that every application must be
> equally drivable from either keyboard or mouse.  There are at least two
> reasons for this:
> 
> - Some people are either unwilling or unable to use a mouse (effectively).
>   While one might not have much sympathy for those unwilling to use mice,
>   the ones `unable' to do so deserve lots of sympathy.
> 
> - Some work environments are too busy and crowded to put a mouse on the
>   persons desk.  Take a look at a stock traders work area sometime.  They
>   have umpty-umpt screens in front of them, and a keyboard for each one
>   lying around, a TV screen (or more), telephones, etc.  No Room for extravagences
>   like mice.
> 

dont forget people who's mouse broke down... Once our mouse broke down, and for a week
I felt really crippled - there were many programs I couldn't use at all or barely use.
I had real trouble choosing the current window on tvtwm, until I added keyboard macros
to make the arrows move you in the icon manager.

> There is a complicated set of rules which a Motif application must follow
> to allow this.  It is all detailed in the style guide, but I have not had
> the time to learn it.  Fortunately the Motif toolkit implements either the
> real thing, or something pretty close.
> 
> The basic idea is that TAB, BACK-TAB, and the arrow keys move keyboard focus
> around the screen.  So if you have a data entry form to fill out the keyboard
> use is: type value, TAB, type value, TAB, type value, ...  When done TAB
> and ARROW to the right button, and hit RETURN.  Voila' no mouse activity
> and much easier than: type, POSITION, type, POSITION, type, POSITION, ..., CLICK.
> 
> The other part is that each widget has a way to trigger actions using the
> keyboard.  Above I mentioned RETURN to activate a button, SPACE works as well.
> The arrow keys scroll listboxes or edit boxes.  The arrow keys also move
> panes in paned windows.  Hitting F10 enable keyboard driving of the
> menu bar, LEFT and RIGHT change which menu pane is pulled down, while UP
> and DOWN change which menu button is enabled.  etc...
> 
> 
> For the application I'm writing at home (SEARCH) it seems that a lot of time
> is being spent moving back and forth between keyboard and mouse.  To cut
> this down I wanted to drive as many things as possible from the keyboard.
> This meant having a mechanism for moving keyboard focus around the screen.
> Once keyboard focus is on a widget, to indicate this to the user.
> 
> So I wrote up a TK module to handle focus setting.  This has gone through
> at least 3 design-coding iterations, and is pretty useful at the moment.
> 
> It does not match up with Motif's style guide in a couple of ways:
> 
> - I could not figure out how to tell TK to bind an action to Shift<Key>Tab.
>   So instead of BACK-TAB, this module uses Control-b.
> 
> - It indicates focus differently from Motif.  Motif draws a border around
>   the outside of the widget.  FOCUS changes the relief of a widget.
> 
> - It does not use LEFT, RIGHT, UP or DOWN for some traversals, Motif does this
>   when there are a group of buttons in the same area.
> 
> - You cannot reach all widgets in the interface.  Only those described to FOCUS.
> 
> 
> A weirdity in FOCUS is that you must initialize it by calling FOCUS:traverse
> after setting up all the traversals.  (Or maybe it's FOCUS:enter?)  Otherwise
> it doesn't work.
> 
> 
> 
> 
> #
> # $Id: focus.tcl,v 1.3 1992/07/21 15:36:33 david Exp $
> # focus -- Functions for implementing keyboard-driven focus traversal
> #
> # This module deals with FocusObjects as its unit-of-discourse.
> # A FocusObject is something which may have focus, and may
> # be traversed through.  It has a couple of subfields as so:
> #
> #	widget to which keyboard focus is to be directed.
> #
> #	widget whose relief is to be modified.
> #
> # When a widget has focus, the relief-widget is made `raised'.  When
> # it loses focus, it is made `flat'.  Normally one will want
> # this relief-widget to be a frame widget.  This is largely because
> # each widget has a `natural & preferred' relief which is part of
> # what identifies that widget to the user.  In order for the relief
> # to show up, the frame must have -borderwidth > 0.
> #
> # Three main entry points exist:
> #
> # FOCUS:OBJ:create -- Creates an FocusObject instance.
> # FOCUS:OBJ:obj    -- Get widget name for the FocusObject's object.
> # FOCUS:OBJ:frame  -- Get the widget name for its frame.
> #
> # FOCUS:circle     -- Sets up a circular chain of FocusObject traversals.
> # FOCUS:bind       -- Sets up more specialized traversals.
> # FOCUS:now        -- Return the FocusObject name where it is NOW.
> #
> # The FocusObjects are stored in a global array called FOCUS_OBJ_heap.
> # The `name' of the FocusObject is used to reference into the heap.
> # Each is stored as a TCL list, with members in the order specified above.
> #
> #
> #
> # $Log: focus.tcl,v $
> # Revision 1.3  1992/07/21  15:36:33  david
> # Create functions to return parts of FocusObject structure, and find
> # the FocusObject which corresponds to a certain widget.  Add support
> # for rebinding the bindings.  Remember where focus currently is.
> #
> # Revision 1.2  1992/07/19  19:41:02  david
> # Change indicator of when a widget has focus.  Used to set background
> # color, now change -raised option on a frame widget.
> # Revamped API to focus module to allow for more terse descriptions
> # of the traversals.  Did this through creation of the FocusObject
> # concept.
> #
> # Revision 1.1  1992/05/27  06:51:32  david
> # Initial revision.
> #
> #
> #
> 
> proc FOCUS:OBJ:create {{name} {object} {frame}} {
> 	global FOCUS_OBJ_heap
> 	set FOCUS_OBJ_heap($name) [list $object $frame ]
> }
> 
> proc FOCUS:OBJ:obj name {
> 	global FOCUS_OBJ_heap
> 	return [lindex $FOCUS_OBJ_heap($name) 0]
> }
> 
> 
> proc FOCUS:OBJ:frame name {
> 	global FOCUS_OBJ_heap
> 	return [lindex $FOCUS_OBJ_heap($name) 1]
> }
> 
> proc FOCUS:OBJ:find object {
> 	global FOCUS_OBJ_heap
> 
> 	foreach name [array names FOCUS_OBJ_heap] {
> 		set obj $FOCUS_OBJ_heap($name)
> 		if {"$object" == [lindex $obj 1] || \
> 		    "$object" == [lindex $obj 0]} {
> 			return $name
> 		}
> 	}
> 	return ""
> }
> 
> # FOCUS:circle -- Create a chain of FOCUS:traversals for the
> #	Objects specified in the arg-list.  Use <Tab> to move forward
> #	through the circle, and <Control-b> to move backwards.  We'd
> #	**LIKE** to use BackTab for moving backwards, but cannot figure
> #	out how to specify that to TK.
> #
> # We destroy any preexisting binding.
> #
> proc FOCUS:circle {args} {
> 	global FOCUS_OBJ_heap
> 
> 	foreach arg $args {
> 		if {![info exists first]} {
> 			set first $arg
> 			set last $arg
> 			continue
> 		}
> 
> 		set to $FOCUS_OBJ_heap($arg)
> 		set from $FOCUS_OBJ_heap($last)
> 
> 		set to_w   [lindex $to   0]
> 		set from_w [lindex $from 0]
> 
> 		catch {bind $from_w <Tab>}
> 		bind $from_w <Tab>       "FOCUS:traverse $last $arg"
> 		catch {bind $to_w   <Control-b>}
> 		bind $to_w   <Control-b> "FOCUS:traverse $arg  $last"
> 
> 		set last $arg
> 	}
> 
> 	if {[info exists last]} {
> 		if {"$first" != "$last"} {
> 			set to $FOCUS_OBJ_heap($first)
> 			set from $FOCUS_OBJ_heap($last)
> 
> 			set to_w   [lindex $to   0]
> 			set from_w [lindex $from 0]
> 
> 			catch {bind $from_w <Tab>}
> 			bind $from_w <Tab>       "FOCUS:traverse $last  $first"
> 			catch {bind $to_w   <Control-b>}
> 			bind $to_w   <Control-b> "FOCUS:traverse $first $last"
> 		}
> 	}
> }
> 
> # FOCUS:bind -- Create special purpose bindings from a particular Object.
> #	The Object is specified in $from_nm.
> #	For each of the args, they are a TCL list of the following members
> #		binding
> #		destination FocusObject name
> #
> # We destroy any preexisting binding.
> #
> proc FOCUS:bind {{from_nm} args} {
> 	global FOCUS_OBJ_heap
> 
> 	set from $FOCUS_OBJ_heap($from_nm)
> 	set from_w [lindex $from 0]
> 
> 	foreach arg $args {
> 		set binding [lindex $arg 0]
> 		set to_nm   [lindex $arg 1]
> 
> 		set to   $FOCUS_OBJ_heap($to_nm)
> 		set to_w [lindex $to   0]
> 
> 		catch {bind $from_w $binding}
> 		bind $from_w $binding "FOCUS:traverse $from_nm $to_nm"
> 	}
> }
> 
> # FOCUS:enter -- Utility to perform the `entering' part of the
> #	focus traversal.
> #
> proc FOCUS:enter {{w} {f}} {
> 	focus $w
> 	$f configure -relief raised
> }
> 
> # FOCUS:leave -- Handle the `leaving' part of traversal.
> #
> proc FOCUS:leave {{w} {f}} {
> 	$f configure -relief flat
> }
> 
> # FOCUS:traverse -- Do the job of traversing from one widget to
> #	the other.  Simply call FOCUS:enter and FOCUS:leave
> #	to do the trick.
> #
> proc FOCUS:traverse {{from_nm} {to_nm}} {
> 	global FOCUS_OBJ_heap FOCUS_now
> 
> 	set to   $FOCUS_OBJ_heap($to_nm)
> 	set from $FOCUS_OBJ_heap($from_nm)
> 
> 	FOCUS:leave [lindex $from 0] [lindex $from 1]
> 	FOCUS:enter [lindex $to   0] [lindex $to   1]
> 
> 	set FOCUS_now $to_nm
> }
> 
> proc FOCUS:now {} {
> 	global FOCUS_now
> 	return $FOCUS_now
> }

-- 
Nadav Har'El                         | ######  ########     # | <-- Sorry if
Email: nyh@gauss.technion.ac.il      |      #       #       # |    you can't
Department of Mathematics, Technion  |      #       #       # |    read Hebrew.
Israel Institute of Technology       | ########     #  ###### |    Nadav. ;)