From AlphaWiki

Main: User Definable Key Bindings

This page is a starting point for implementing user-definable key-bindings in AlphaTcl.

Vince -- I don't so much care for the details (i.e. procedure bodies) of any implementation at this stage, but I do want to see a good overall design --- a simple, consistent way in which menus can declare their key-bindings and a variety of binding sets can be provided, and the user can override anything they like. If we can also add the ability for 'localization', even better. So, I think we need to:

And that's it, really. If we can first find what we think is a good design for the future, then second we can consider how to transition Alpha(Tcl) to that point, and if any intermediate steps are needed. Only at that stage should we start worrying about implementing this.

The following sections are discussions about addressing these needs.


<JK, Feb2005>: I think the above phrase

 
  a simple, consistent way in which menus can declare their key-bindings

is a misconception. Menus should not *declare* bindings, they should merely reflect bindings.

It can sometimes be extremely difficult to turn keybindings OFF. One of the biggest problems is when the binding is defined by a menu -- you need to modify the menu! And if the menu is rebuilt dynamically then you are simply out of luck -- you need to dig into its Tcl code.

The error is to think that a shortcut is a shortcut to a menu item. It is really a shortcut to the *command* -- the menu item is also just reference to the command, but in fact rather a detour (its like reading a catalogue, which is superfluous if you already know what you want to order). The cool thing about menus is that you can browse around and give the computer instructions without having to remember the names of the commands. But if you already know what you want it is a waste of time to browse menus, then you should use the keyboard shortcut instead. For this reason, the menu shortcuts should carry this little advertisement of any corresponding keyboard shortcut, so that each time you choose the menu you are reminded of the better sort of shortcut, and eventually you don't have to use the menu any more.

From this viewpoint, the menu interface is only a temporary solution, a pedagogical gadget to teach you the true shortcuts. Once you dominate the programme, it should never be necessary to use menus. So it is completely backwards that the menu should *define* the shortcut. It ought to be merely an advertisement, a reflection of a fact. The backwardsness implies that menu related problems can interfere with direct communication between the user and the programme. It's like in a restaurant, if an incoherence in the menu (a mere piece of cardboard made for the tourists to help them get an overview of the possiblities) would prevent you from saying what you want to eat, even if you know the chef and are speaking directly to him...

Just like in Tk: the symbol in the menu is just a decoration.

So: eliminate all the </B/U/I business from the menu syntax. There should only ONE database where key bindings are stored, and a clean and coherent interface to this database (Bind and unBind). It should be possible to clear an entry in this database without having to know its value (unlike currently where you have to know what a key is bound to before you can unbind it). In this database, each entry can subscribe to the advertising service of the menus. This means that in the definition of the menus there is no reference to shortcuts. Instead the menu building procs look up in the database to see if some decoration is required. When the user presses a key combination, the programme consults the database to see if a sepcial action is required. When the user is surprised by some special action he consults this same database (through a nice graphical interface).

The API for this database is not just setting entries: the two main commands, Bind and unBind, take care of adjusting the menus too. That is, when a Bind instruction is issued, look in the database is some menu advertisement is registered for the command, and then rebuild that menu to reflect the change. I guess this is the whole point: those symbols in the menus are just the reflection of a deeper reality, not a constitution of it.

Next step in the implementation is to design the database carefully. You need to be able to lookup in it in both directions, and for a given command there may be many different shortcuts. I think the cleanest solution will be to require explicit registration for menu decoration. I.e., just the presence of a pair

 
  Cmd-/   zoom

does not imply that / is marked in the Zoom menu item. Because there might be other keys bound to this command, and we can't have two decoration in one menu, and we don't want to let it be up to the menu building proc to choose... So there should be a third entry in the row, containing the name of the menu item for which this binding should be displayed. (And it would be an error to have two rows with the same menu-registration, so the Bind proc should perform this integrity check.)

Well, there are some things to sort out, but I am sure it would be worth the trouble, to have such a clean interface to keybindings.

Vince says he totally agrees with the above. Let's rename Bind, unBind to alpha::Bind, alpha::unBind (doing just what they do now), but let's add some AlphaTcl code to do exactly what you suggest above, for example:

 
    Bind  ?-set <name>? ?-menus $menuDescriptor? $keys $tclScript $descriptiveName ?$bindtag?

where menuDescriptor is a list of ?menuname menuitem? ... ?menuname menuitem? (no reason to prevent showing the same binding in multiple menus). For example:

 
    Bind -menus [list $openWindowsMenu zoom] Cmd-/ \
      zoom "Maximize the current window to fill your screen"

    Bind -set Windows -menus [list $openWindowsMenu zoom] Cmd-/ \
      zoom "Maximize the current window to fill your screen"

Here the 'set' option lets us define different binding sets for different platforms, or even for different users to provide their own sets. I suppose we would define a precedence relation between sets.

The Bind command would just register it's entry in the Binding database. If any $menu is already built, it would also set any accelerators in those menu entries. Then it would execute alpha::Bind to create the actual binding, unless it is overridden by something else. If it overrides some other binding, that binding would be deactivated. If we choose to pay attention to menu item enabled-state, then we'd want to check that before calling alpha::Bind.

Questions/issues:


(jeg) I had the same (although not quite as strenuous) reaction that Dominique had to Joachim's remarks. It is a very old principle of the Macintosh HIG that key-bindings are shortcuts for menu items and that everything available for convenience through a key binding must be available through the GUI (whether via menus or some other mouse-clickable element). Alpha violates this principle left and right, and to some extent that is never going to change; nonetheless, I think we should strive to adhere to it to the greatest extent. Even Joachim's comments acknowledge the utility of menus with displayed bindings for the educational value; I would just counter his remark

 
  Once you dominate the programme, it should never be necessary to use menus.

with

 
  It should never be necessary to use key bindings to dominate the programme.

Not that I would ever accuse Joachim of taking an extreme position for effect, but I don't know that we're actually that far apart. I think Lars' emphasis on actions underlying both key bindings and menu elements is not that far from Joachim's point about commands, and I generally agree with both.

I think Lars makes a very important point about having a unified interface, so that you can ascribe an action to a key binding and/or you can ascribe an action to a menu command. While I agree that 'menu::textEditProc Edit paste' is not very 'humane', I don't think I agree that it's desirable to maintain a separate way to bind to such 'inhumane' scripts. How would a user know, then, why a particular action binding didn't work and they got something inhumane instead? (Or conversely why, when they 'hacked' a Bind to an inhumane script, they got some menu command instead?) Some choice of precedence has to be made, and as soon as you do, you've got the same obnoxious bifurcation that we have now between menu bindings and Bind bindings.

I think we should realize that, for the purposes of most users, the text of the menu item is the action/command. Any interface that allows users to edit bindings should allow them to see what menu item(s) are associated with it.

Something like:

 
  Command-O         |   File > Open              (set...)
  Option-Command-O  |   File > Open Via Fileset  (set...)
                    |   Fileset > Open File      (set...)

Presently we have

 
  Bind    'o' <co>  {filesetMenu::menuProc ‚ΔΆ131 {Open File}} 
  Bind    'o' <co>  {menu::fileProc File openViaFileset}

where both of the inhumane scripts {filesetMenu::menuProc ‚ΔΆ131 {Open File}} and {menu::fileProc File openViaFileset} are bound to the underlying action of 'file::openViaFileset', through respectively mild and spicy shenanigans. I don't know that most users will ultimately care about 'file::openViaFileset', though. This command is pretty readable, but many are not. Perhaps that points at the answer, though: maybe all 'normal' menu items and key bindings that an average user might want to edit should be bound to a human-readable Tcl command. Then perhaps the interface could be:

 
  file::openViaFileset  | Command-Option-O         (set...)
                        | File > Open Via Fileset  (set...)
                        | Fileset > Open File      (set...)

All listed key bindings and menu items are equivalent accesses to the action 'file::openViaFileset'. As Lars suggests, when the menu items display themselves, they can query their action for the appropriate key binding (in the case of more than one binding, I guess the first, or perhaps the shortest, would be displayed, modulo not having any conflicts with other actions I suppose).

If you want to bind to an 'inhumane' action to either a key combination or to a menu item, so be it, but by default, the actions listed would be as clear as possible.

As Lars points out, this model is not very consistent with our present switch-dispatching menu procs, but it is more consistent with the commandID model of Carbon Events, and perhaps more consistent with Tk's model as well.


(cbu) 03Mar05 User Issues

Much of the previous discussion has centered on how the core creates shortcuts using Menu]] and [[Bind, as well as underlying philosophies and design principles for defining them, and proposals for doing it differently.

I don't think that these are directly relevant to the issues surrounding user-definable menu bindings. As I have said elsewhere, to address this in AlphaTcl we need

These elements are required no matter how the core uses the information to create the shortcuts.

These are some of the UI issues that prompted this discussion:

Comments:

menus are fine, but the TeX and HTML mode menus go overboard. I think we should focus on (a) a sensible default setup and (b) an intuitive UI to change it.

two different menu items that use the same shortcut. That's simply absurd. I don't care how fancy/advanced/complex Alpha is, we need guidelines so that no mode will ever by default over-ride a default global shortcut. If the user chooses to create such a conflict, we can point this out.

to redefine that menu's AlphaTcl build code to get rid of them) would simply disappear if you could change the defaults. And fewer defaults, of course, would result in fewer global/mode conflicts in the first place, as would a good specification for defining mode shortcuts.

then get on with their editing and not worry about the rest. As with most preferences, changing menu shortcuts is not going to be a daily activity. Even if the initial UI for doing this was a bit clumsy, I still think that slow-loading, nested dialogs would be preferable to hacking AlphaTcl code. With improved dialog support we can always improve the UI later, such as adding tabbed dialogs and including an improved "Set Binding" interface similar to the MacOSX System Preferences > Keyboard Shortcuts dialog.


(cbu) 03Mar05 Menu Item Procedure Declarations

A couple more thoughts:

(None of this addresses the API for defining default menu shortcuts or the UI for changing the supplied defaults, but those are separate issues.)

(1) Instead of supplying the name of a menu proc in Menu, the core could just automatically route all menu items through a procedure named alpha::menuProc (defined in AlphaTcl), as in

 
    alpha::menuProc "File" "Save"

alpha::menuProc would then determine if there is a specific menu proc to call (one registered elsewhere in AlphaTcl) or if a general menu proc (also previously registered) should be used, i.e.

 
    variable menuItemProcs
    variable menuNameProcs
    variable menuNamespaces

    if {[info exists menuItemProcs([list $menuName $itemName])] {
	# A specific procedure has been defined for this item.
        uplevel \#0 [list $menuItemProcs([list $menuName $itemName])]
    } elseif {[info exists menuNameProcs($menuName)]} {
	# A specific procedure has been defined for this menu.
        uplevel \#0 [list $menuNameProcs($menuName) $menuName $itemName]
    } elseif {[info exists menuNamespaces($menuName)]} {
	# A specific namespace has been defined for this menu.
	set nameSpace [string trimleft $menuNamespaces($menuName) ":"]
	uplevel \#0 [list ::${nameSpace}::${itemName}]
    } elseif {[info procs ::[string trimleft $itemName ":"]]} {
	# A global procedure has been defined for this item.
        uplevel \#0 [list ::[string trimleft $itemName ":"]]
    } else {
	# We don't know what to do with this menu/item combination.
        error "Cancelled -- no registered procedure for '$itemName'"
    }

The user would then know that all menu items are routed through a single procedure (i.e. this is close to a "humane" description) but AlphaTcl developers have a high degree of flexibility in determining how the proper procedure will be called.

We could actually begin implementing something like this right now via the arguments that menu::buildOne]] supplies to [[Menu, i.e. intervene to register menu name procedures and substitute alpha::menuProc in the ultimate Menu call.

(2) menu::buildOne]] could explicitly call [[Bind to create the shortcuts. The core could supply the glyphs next to the menu item simply as "hints" as suggested by Joachim. At present, this would require parsing out the binding information from the supplied menu list, but we could easily enhance the menu::buildOne API to make this easier. I'll provide some ideas on this in later post.

If this was adopted we could (for back compatibility) place a wrapper in AlphaTcl around the current Menu command to capture and register any "-p <procName>" argument and to create the bindings. Or create a new alpha::createMenu] core command and turn [[Menu into a true AlphaTcl proc that parses the args and calls this.


(cbu) 03Mar05 API for Menu/Shortcut Creation

As an AlphaTcl programmer, I want a simple method for defining both a default shortcut for a menu item and a procedure that will be called. We already have menu::buildProc, I think that we could expand/enhance that method to make things more transparent. For example, what if each item in the list of menu items was itself a list containing

All but the first item would be optional.

Something like this:

 
    proc file::buildFileMenu {} {

        global alpha::macos fileMenu

        if {$::alpha::macos} {
            set openViaFilesetMods  <co>
            set closeAllMods        <co>
            set saveAllModes        <co>
        } else {
            set openViaFilesetMods  <cz>
            set closeAllMods        <cz>
            set saveAllModes        <cz>
        }

        set menuList [list \
          [list "new..."   'N' <c> 0 "Create a new window"] \
          [list "open..."  'O' <c> 0 "Open a local file"] ]
          [list "openViaFileset..." 'O' $openViaFilesetMods 0 \
          "Open a file via a pre-defined fileset" "::file::openViaFileset"] \
          [list "close"    'W' <c> 1 "Close the active window"]
          [list "closeAll" 'W' $closeAllMods 2 "Close all windows"] \
          [list "(-)"] \
          [list "save"     'S' <c> 1 "Save the active window"] \
          [list "saveAll"  'S' $saveAllModes 2 "Save all windows"] \
          ...
          [list "-menu" "fileUtils"]
          ]

        set subMenus [list "fileUtils"]
        set menuProc [list "::file::menuProc"]

        return [list "bind" $menuList $menuProc $subMenus $fileMenu]
    }

In menu::buildOne, "bind" would be interpreted as a special "build" type indicating that the $menuList variable includes suggested shortcuts suggestions that should be registered as default values, and if the user has already re-defined them then the user's prefs are used instead.

It is then the responsibilty of menu::buildOne to figure out how these bindings are actually created, i.e. it is at this point that the issues previously discussed would come into play. Using the current core Menu command, something like {'n' <c>} would have to be parsed into {<O/N}, and the "helpTag" argument would simply be ignored. If (after later changes) the core simply supplied shortcuts "hints" in the menus, menu::buildOne would create the shortcut itself using Bind.



Define an API for creating menus

(if different from the current Menu ...)


Define an API for defining key-bindings

(if not already included in the menu definition ...)


Define what we would like a good dialog/user-experience to look like for editing these prefs

(which may require changes to Alpha's core ...)


Define resulting changes to enableMenuItem]], [[markMenuItem etc

(if any such changes are required ...)



Archives

The sections below include information about possible implementations, but further discussion should be postponed until an outline for the overall design has been determined.


(cbu) I see four main requirements for this feature:

The following sections are discussions about addressing these needs.

Vince Note: a lot of this page seems to be heavily constrained by what can easily be done in a few lines of code in AlphaTcl at present. Let's try to think beyond that to how we would (in an ideal world) like to set menu bindings. What do other applications use for this? What kind of dialog?


Creating a list of default bindings

(cbu) We already have [[newPref menubinding ... that is used throughout AlphaTcl to set/store default menu bindings which can then be changed by the user and to create menus. It would probably make sense to create a new bindingPref procedure which is tailored to our needs, of the form

 
    bindingPref <menuName> <itemName> <defaultBinding>

Each package that adds new menu items would define bindings for them. We would also need a "hide/show" mechanism for those packages that replace menu items rather than simply adding new ones. (Prefs defined with bindingPref would initially be shown, some other procedure would explicitly hide a previously defined binding.)

Vince adds that this has always struck me as rather unwieldy. Wouldn't it be easier to understand and read if they were either in a similar syntax to or even part of the menu definition:

 
    userdefinable::Menu File {
      "close" "<E<S/W" 
      "closeFloat" "<S<O<U/W"
      "closeAll" "<S<I<O/W" 
      "save" "<E<S/S"
      "saveUnmodified" "<S<B<O/S" 
    }

or,

 
    userdefinable::Menu File {
      {"close" "<E<S/W" help-text etc} 
      {"closeFloat" "<S<O<U/W"}
      {"closeAll" "<S<I<O/W"} 
      {"save" "<E<S/S"}
      {"saveUnmodified" "<S<B<O/S"}
    }

etc.

(cbu) Sure, that seems perfectly reasonable. The main issue in this section is to create a set of default bindings that would be stored somewhere so that they can be reconciled with any user bindings later when the menu is actually built. It doesn't matter to me how this is done -- your userdefinable::Menu procedure (or, keeping it in the menu namespace perhaps menu::defaultItemsBindings) could be easily used to create an array with

 
    proc menu::defaultItemsBindings {menuName itemList} {
	variable defaultMenuBindings$menuName
        # Create the array of default menu bindings.
        eval [list array set defaultMenuBindings$menuName] $itemList
        # Create the list of initial menu items.
        variable defaultItems
        set defaultItems($menuName) [list]
        for {set i 0} {($i < [llength $itemList])} {incr i 2} {
            lappend defaultItems($menuName) [lindex $itemList $i]
        }
        return
    }

(using the first form.) Maybe dict should be used here, but I'm not very familiar with its usage yet.


Dealing with dynamic menu pairs

(cbu) AlphaTcl already has built in support for this. When a menu binding pref has "/" in it, it is assumed to be a dynamic pair. The "Option" checkbox is then missing from the Set Binding dialog, as we assume that Option will be used to display the 'hidden' item.

This is used extensively by HTML mode.

Another minor example is the Mail Menu, which uses

 
    if {($::alpha::platform eq "alpha")} {
	newPref menubinding reply/ReplyToAll "<U<B/R" \
	  Mail {Mail::tcllib::updatePreferences}
    } else {
	newPref menubinding reply           "<U<B/R" Mail {Mail::updatePreferences}
	newPref menubinding replyToAll    "<U<B<O/R" Mail {Mail::updatePreferences}

to define the preferences, and

 
    if {(${alpha::platform} eq "alpha")} {
	menu::insert mailMenu "items" [list "after" "\(-"] \
	  "<E<S$MailmodeVars(reply/ReplyToAll)reply" \
	  "<S<I$MailmodeVars(reply/ReplyToAll)replyToAll"
    } else {
	menu::insert mailMenu "items" [list "after" "\(-"] \
	  "$MailmodeVars(reply)reply" \
	  "$MailmodeVars(replyToAll)replyToAll"
    }

to insert them. (c.f. "mailMode.tcl" [http://cvs.sourceforge.net/viewcvs.py/alphatcl/Tcl/Menus/Mail%20Menu/mailMode.tcl?view=markup] and "mailTcllib.tcl" [http://cvs.sourceforge.net/viewcvs.py/alphatcl/Tcl/Menus/Mail%20Menu/mailTcllib.tcl?view=markup])

So it is very easy to create a list of menu items based on platform, and then create a list of bindings that correspond to them.


Reconciling the bindings

(cbu) This should probably take place in [[menu::buildOne, after all insertions and/or replacements have been dealt with. We'll have a list of menu items, and so we simply check each one to see if a binding preference has been registered for it or not. If so, we then check to see if the user has defined a new binding, if not then use the provided default.


Providing a User Interface

(cbu) AlphaTcl already has several UI examples for user defined menu bindings. The "AlphaDev" menu is one -- this uses newPref menubinding ... to create the preferences, and then the standard prefs dialog sorts them out and includes a "Set" button for each one. (See AlphaDev > AlphaDev Menu Bindings.)

The Config > Special Keys dialog is another example. Here the current preferences are set and stored in an array, and this is manipulated to create the dialog (and then reset the bindings) in global::specialKeys.

I imagine a multi-stage listpick dialog scheme to keep it all manageable for the user. The first contains all menubar menus, i.e. File through OpenWins plus user activated menus such as Filesets Ftp WWW etc. One that is selected, a second listpick dialog appears with the option Main Menu plus all submenus. For example, File would potentially include

Each package that adds new menu submenus would need a mechanism for declaring a new option to be listed in this second dialog.

Selecting one of these submenu categories would then either

(1) Call a procedure defined specifically for that menu (as for current packages like the AlphaDev menu that already have a mechanism for setting the bindings and rebuilding the menus, or

(2) Call a default SystemCode routine that opens a Set Bindings dialog with the relevant items that have been previously registered by bindingPref and are currently not hidden.

Crucial to this UI (in my opinion) would be the ability to restore the default bindings without requiring a restart. This is one reason why newPref]] might be inadequate for our needs. A [[bindingPref procedure should not only check for a previously set value for the binding preference, but also remember the default so that it can be used again if necessary. This should be a separate Restore Bindings dialog which follows the same scheme as above, but limits the options to those menus (and then submenus) for which user defined bindings exist. The final dialog would be another listpick with those specific menu items whose bindings can be restored.

Vince Repeated selections from a listpick seems like a very unhelpful way.

(cbu) True enough about the [[listpick bit. We could easily have a single dialog called by "Config > Set Menu Keys" where we have a separate pane for each possible menu category. That's the basic design of BBEdit. When we get tabbed dialogs in AlphaX this would be even easier to navigate.


Potential Complications

(cbu) Some challenges that I foresee are mainly for back compatibility. For example, if we have code like

 
    menu::insert File items "<E<B<O/OopenRemote" "<S<I<O/OopenViaFileset"

we want to ensure that this will work no matter what the current binding is for "openRemote". In other words, the above command should be equivalent to

 
    menu::insert File items "openRemote" "<S<I<O/OopenViaFileset"

or

 
    menu::insert File items "openRemote" "openViaFileset"

This means that in menu::buildOne all replacement/addition items would need to be parsed to make sure that only the item name is used to determine the proper location, or to remove an item.


Category.Development

Retrieved from http://alphatcl.sourceforge.net/wiki/pmwiki.php/Main/UserDefinableKeyBindings
Page last modified on January 23, 2006, at 10:50 PM