jcommand.tcl

Introduction

The jcommand.tcl library provides utility procedures for working with user­visible commands (i.e., procedures which the user can invoke directly, e.g. by invoking them from a menu or pressing a button). The jcommand.tcl library is useful because command procedures designed for it can easily be assigned to buttons and menus, and accelerator bindings for them can be automatically created. It is tightly intertwined with the jldb.tcl library, which allows the user­visible names of commands to be localised (by the user or by a site administrator) and associates command procedures with accelerator labels and keyboard bindings.

The jcommand.tcl library is distributed as part of the jstools package.

This document describes jcommand.tcl version 1998.09.30.

Usage

Accessing the Library


In order to use the jcommand.tcl library, it (and any other libraries it depends on) must be in your Tcl auto_path, described in tclvars(n). Information about how to arrange that, and other conventions common to the jstools libraries, is in the Usage section of The jstools Libraries.

Credits and Copyright

Author

Jay Sekora
js@aq.org
http://www.aq.org/~js/

Copyright

The library is copyright © 1992-1998 by Jay Sekora, but may be freely redistributed under the conditions at the top of the file.

Command Procedures

The jcommand.tcl library is designed to use and manipulate command procedures, which are Tcl procedures (or, theoretically, Tcl commands implemented in C) which implement user­visible commands. I.e., these are the actions your users should be able to perform, whether by clicking on a button, choosing a menu entry, pressing a keyboard accelerator key, or even typing a command.

Defining Command Procedures

Command procedures should have a procedure definition along the following model:
proc app:cmd:cmnd { w args } {
# body of procedure
}
where app is the name of your application and command is an identifier for the particualar command.

(Currently, I don't think the library actually enforces any constraints on the procedure name, but the format above (app:cmd:cmnd) is what I use, and (1) I'm not completely certain I'm not assuming those kinds of names anywere and (2) I'm not completely certain I'll never assume those kinds of names in the future.)

The argument w is a window name, intended to identify the document the command is intended to apply to in multi­document applications. It's required even if your application only supports one toplevel window; you can just invoke your commands with `.' as the value of w and ignore it in your procedure definitions if that's the case. Sometimes you (the programmer) provide the value of w - for instance, when you define a menu of command procedures, you can specify the window they apply to. Sometimes the value of w is less constrained - for instance, users can invoke command procedures with keyboard accelerators, in which case the name of the widget the accelerator binding was invoked for is provided as w. Depending how complicated your user interface is, and what widgets keyboard accelerators are active in, this may mean that your command procedures need to translate widget names to document names.

The special argument keyword args should be included in case you want your command procedure to interact with the jbindtext.tcl and jbindentry.tcl libraries which support editor emulation. They call procedures with additional arguments (which your command procedures can generally ignore) after the widget name. For instance, the jedit application defines both Control-s in the emacs-normal map and slash (/) in the vi-command map as the command procedure jedit:cmd:find_forward, so that Emacs users and vi users can use their familiar keystrokes to search for text. (Actually, please include it even if you don't intend to make your command procedures available this way, since future versions of the library may require it.)

Command Procedures and the Language Database

Normal usage of the jcommand.tcl library is entwined with the natural­language database mechanism provided by the jldb.tcl library. You should register default natural­language names for your command procedures (the labels the user sees on menus, such as `Save As...' or `Help on Recipe Manager') in your codew the j:command:register procedure, but those names can be overridden by entries in the natural­language database (either site­wide or per­user). Also, there are a number of features - keyboard accelerator bindings, underlining of Alt­key menu shortcuts, and optional shorter versions of command names for use on buttons - that can only be defined in the language database. So it's possible to use the command procedure library without a language database, but your users will see more functionality if you provide one. (This was partly a deliberate decision to encourage authors to provide language databases, to make it easier for sites and individuals to customise applications using the jstools libraries to their own language.)

Command Procedures and Menus

The jcomands.tcl library itself includes procedures to create individual buttons and buttonbars, but menu support is provided by the jmenu.tcl library provides support for using command procedures in menus. (It provides a few other features as well.) It's possible to intersperse command procedures (in the sense of this library) and ordinary Tcl code in the same menu.

An Extended Example

The jdoc application includes, among others, the following two command procedures:

##################################################
# jdoc:cmd:save_as - save to a file
##################################################

j:command:register jdoc:cmd:save_as {Save As...}
proc jdoc:cmd:save_as { w args } {
j:tc:saveas $w
}

##################################################
# jdoc:cmd:quit - quit
##################################################
j:command:register jdoc:cmd:quit {Quit}
proc jdoc:cmd:quit { w args } {
if [j:confirm -text "Are you sure you want to quit?"] {
exit 0
}
}

(Notice that jdoc:cmd:quit ignores its window argument.)

The following procedure would create a new `File' menubutton $mb with two menu entries which would invoke the above two prodedures on the window w:

proc jdoc:mkmenu:file { mb w } {
j:menu:menubutton $mb $mb.m {File}
pack $mb -side left

j:menu:commands $mb.m $w {
jdoc:cmd:save_as
jdoc:cmd:quit
}

By default (i.e. if there is no language database), the menubutton will be labelled `File' and the menu entries will be labelled `Save As...' and `Quit'. If you provide a language database, though, you can also provide Alt-key equivalents and other keyboard accelerators. For instance, if the active language database contained

j:ldb:set_strings {
{jdoc:cmd:save_as \
{Sauvegarder comme...} 0 <Control-Key-s> {^S}}
{jdoc:cmd:quit {Quitter} 0 <Key-F3> {F3}}
{File Fichier 0}
}

then the labels would be in French (or my pathetic attempt at it, anyway :-), and the first character of each label would be underlined for Alt-key menu traversal. The strings `^S' and `F3' will be displayed to the left of the appropriate menu entries, but this by itself isn't quite enough to define keyboard accelerators. However, if the application executes the command

j:command:bind .t .t [j:command:list]

then keyboard accelerator bindings will be defined for all registered commands. The accelerators will be active when widget .t had the focus and they will also act on widget .t (i.e., they will invoke command procedures with .t appended as an argument).

Overview

Procedures

j:command:register - register the existence of a command procedure
j:command:list - list all currently­registered command procedures
j:command:button - create a button to perform a command procedure
j:command:buttonbar - create a set of command procedure buttons
j:command:bind - set up bindings for command accelerators

j:command:register

Usage

j:command:register cmd defaultname

Arguments

cmd - command procedure name
defaultname - default (natural­language) name for command (default {})

Example

j:command:register jedit:cmd:copy Copy
proc jedit:cmd:copy { w args } {
global JEDIT_CUTBUFFER
set JEDIT_CUTBUFFER [$w get sel.first sel.last]
}

Description

This procedure records the existence of a command procedure called cmd, and, if defaultname is provided and is non­null, uses j:nls:set_defaults to set defaultname as the default natural­language string for the key cmd.

The net result of this is that cmd will be included in the list that j:command:list returns, which provides a list of the valid user commands in the application, and that if defaultname is given, cmd is guaranteed to be a valid entry in the natural­language database, and will produce defaultname if there's no entry for cmd in a loaded database file.

See j:command:list for a discussion of exactly where in your code you should register commands.

j:command:list

Usage

j:command:list

Example

j:command:bind {.text .entry} .main [j:command:list]

Description

This procedure returns a list of the currently registered command procedures (user­invokable commands) in your application, as specified in previous calls to j:command:register. This list can be used (as in the example above) to automate bindings for command accelerators. It could also be used, for instance, to populate a listbox with a list of the valid commands in your application, for instance, to allow the user to choose commands to enable or disable.

You may need to be careful that all the commands in your application have been registered at the time you call j:command:list. If you auto­load command procedures and include the j:command:register invocation in the library file where the procedure is defined, if the file hasn't been auto­loaded when you invoke j:command:list, it won't be included in the result.

One way around this is to put all your j:command:register invocations in initialisation code called near the beginning of your application; however, this may increase the likelihood that you'll forget to register a new command you add.

Another alternative is to include a dummy procedure (that doesn't do anything) in each library file that contain command procedures, and call that in your initialisation code; that will force the file to be auto­loaded (at the cost of slower startup time, which defeats some of the purpose of library files).

j:command:button

Usage

j:command:button b w cmd

Arguments

b - name of new button to create
w - window argument for the command procedure
cmd - command procedure to be performed by button b

Examples

j:command:button .more .text view:cmd:more

pack [j:command:button .quit . ask:cmd:quit]

Description

This procedure creates a new button b, which will execute the command cmd with w as its single argument when the user clicks it. The text displayed on the button will be looked up in the natural­language database via j:nls:short using cmd as the key; see jldb.tcl for more information.

(Because the key is looked up using j:ldb:short, whereas strings in menus are typically looked up using j:ldb, it's easy, although not necessary, to have different strings displayed for the same command on buttons and in menus.)

The pathname b of the button created is returned.

The button will be at least eight characters wide, even if the text corresponding to key is shorter than that. This is intended to provide some visual consistency to buttons with short labels. (Of course, you should be aware when designing your layout that the length of the text displayed in a button will vary from language to language, so you can't count on predicting the width of the button created.)

The perhaps odd order of the arguments to j:command:button, with the argument to cmd preceding cmd itself, is intended to be consistent with the order of arguments to j:command:menuentries, where the list of commands is last because there may be many of them.

j:command:buttonbar

Usage

j:command:buttonbar frame [options] w cmds

Arguments

frame - pathname of the new frame widget to create
w - window to connect commands to; argument to each of commands
cmds - list of command procedures

Options

-default defaultcmd (default (NONE))
-padx padx (default 5)
-pady pady (default 5)
-orient horizontal|vertical (default horizontal)

Example

j:command:buttonbar -default test:cmd:help .b . {
test:cmd:load
test:cmd:save
test:cmd:quit
test:cmd:help
}
pack .b -side bottom -fill x

Description

This procedure creates a horizontal (by default) row of buttons, with some spacing between and around them. Each element cmd in the cmds will create a button with the name frame.cmd which will execute the corresponding command, with w as its sole argument, when it is invoked.

Each button will be labelled with the text corresponding to cmd in the natural­language database. The buttons will be created from right to left, and each button will be at least eight characters wide (for visual consistency). The button corresponding to the command default (if any) will be surrounded with a sunken rectangle.

If given, padx and pady specify the space to add around the edges of the group of buttons. The default is to add five pixels around the group of buttons. These options do not affect the spacing between buttons..

If -orient vertical is specified, the buttons will be laid out vertically (starting at the botton), rather than horizontally.

You may find j:default_button (in jtkutils.tcl) useful with j:command:buttonbar, since no bindings are automatically set up to invoke the default button.

j:command:bind

Usage

j:command:bind widgets w cmds

Arguments

widgets - list of widgets for which bindings should be created
w - argument to be appended to each procedure in cmds
cmds - list of command procedures whose accelerators should be bound

Examples

j:command:bind {.text .entry .} .text [j:command:list]

j:command:bind .find.search.e .text {
view:cmd:quit
view:cmd:help
}

j:command:bind {Text Entry} .main [j:command:list]

Description

This procedure looks up the accelerator event sequences for each of the command procedures in cmds (using the natural­language database described in jldb.tcl) and creates bindings for them for each widget in widgets, which can be either full widget pathnames or widget class names. When such a binding is invoked, the corresponding command procedure will be called with w as its single argument.

The first example above is typical; if you use j:command:register to register the commands in your application, you can use j:command:list later to get a list of all of them. However, you can also specify a list of command procedures explicitly, as in the second example. You might want to do that, for instance, if your application has two windows and you want all the bindings in effect in one of them, but only a subset in the other.

Bugs and Limitations

* Normal use of these procedures will result in multiple calls to the jldb.tcl procedures to look up the same information, which is a minor performance hit.

Future Directions

* If and when I stop supporting Tk 3.6, j:command:menuentries will be able to add automatic accelerator bindings, taking advantage of Tk 4.0's more elaborate binding mechanism.

* There should be a way to ask j:command:button to underline a character or display an accelerator.

* There should be a way to tell j:command:button not to enforce a minimum width, and there should also be a way to force an explicit width.