Go to the first, previous, next, last section, table of contents.
GCL-TK is a windowing interface for GNU Common Lisp. It provides the functionality of the TK widget set, which in turn implements a widget set which has the look and feel of Motif.
The interface allows the user to draw graphics, get input from menus, make regions mouse sensitive, and bind lisp commands to regions. It communicates over a socket with a `gcltksrv' process, which speaks to the display via the TK library. The displaying process may run on a machine which is closer to the display, and so involves less communication. It also may remain active even though the lisp is involved in a separate user computation. The display server can, however, interrupt the lisp at will, to inquire about variables and run commands.
The user may also interface with existing TCL/TK
programs,
binding some buttons, or tracking some objects.
The size of the program is moderate. In its current form it adds only about 45K bytes to the lisp image, and the `gcltksrv' program uses shared libraries, and is on the order of 150Kbytes on a sparc.
This chapter describes some of the common features of the command structure of widgets, and of control functions. The actual functions for construction of windows are discussed in section Widgets, and more general functions for making them appear, lowering them, querying about them in section Control.
Once GCL has been properly installed you should be able to do the following simple example:
(in-package "TK") (tkconnect) (button '.hello :text "Hello World" :command '(print "hi")) ==>.HELLO (pack '.hello)
We first switched to the "TK" package, so that functions like button
and pack would be found.
After doing the tkconnect, a window should appear on your screen, see See section tkconnect.
The invocation of the function button
creates a new function
called .hello
which is a widget function. It is then
made visible in the window by using the pack
function.
You may now click on the little window, and you should see the command executed in your lisp. Thus "hi" should be printed in the lisp window. This will happen whether or not you have a job running in the lisp, that is lisp will be interrupted and your command will run, and then return the control to your program.
The function button
is called a widget constructor, and the
function .hello
is called a widget. If you have managed to
accomplish the above, then GCL is probably installed correctly, and you
can graduate to the next section! If you dont like reading but prefer
to look at demos and code, then you should look in the demos directory,
where you will find a number of examples. A monitor for the garbage
collector (mkgcmonitor), a demonstration of canvas widgets (mkitems),
a sample listbox with scrolling (mklistbox).
A widget is a lisp symbol which has a function binding. The
first argument is always a keyword and is called the option.
The argument pattern for the remaining arguments depends on the
option. The most common option is :configure
in
which case the remaining arguments are alternating keyword/value
pairs, with the same keywords being permitted as at the creation
of the widget.
A widget is created by means of a widget constructor, of
which there are currently 15, each of them appearing as the title of a
section in section Widgets. They live in the "TK"
package, and for
the moment we will assume we have switched to this package. Thus for
example button
is such a widget constructor function. Of course
this is lisp, and you can make your own widget constructors, but when
you do so it is a good idea to follow the standard argument patterns
that are outlined in this section.
(button '.hello) ==> .HELLO
creates a widget whose name is .hello
. There is a parent child
hierarchy among widgets which is implicit in the name used for the
widget. This is much like the pathname structure on a Unix or Dos
file system, except that '.'
is used as the separator rather
than a /
or \
. For this reason the widget instances
are sometimes referred to as pathnames. A child of the
parent widget .hello
might be called .hello.joe
, and
a child of this last might be .hello.joe.bar
. The parent of
everyone is called .
. Multiple top level windows are created
using the toplevel
command (see section toplevel).
The widget constructor functions take keyword and value pairs, which allow you to specify attributes at the time of creation:
(button '.hello :text "Hello World" :width 20) ==>.HELLO
indicating that we want the text in the button window to be
Hello World
and the width of the window to be 20 characters
wide. Other types of windows allow specification in centimeters
2c
, or in inches (2i
) or in millimeters 2m
or in pixels 2
. But text windows usually have their
dimensions specified as multiples of a character width and height.
This latter concept is called a grid.
Once the window has been created, if you want to change the text you do NOT do:
(button '.hello :text "Bye World" :width 20)
This would be in error, because the window .hello already exists. You would either have to first call
(destroy '.hello)
But usually you just want to change an attribute. .hello
is
actually a function, as we mentioned earlier, and it is this function
that you use:
(.hello :configure :text "Bye World")
This would simply change the text, and not change where the window had
been placed on the screen (if it had), or how it had been packed
into the window hierarchy. Here the argument :configure
is
called an option, and it specifies which types of keywords can
follow it. For example
(.hello :flash)
is also valid, but in this case the :text
keyword is not permitted
after flash. If it were, then it would mean something else besides
what it means in the above. For example one might have defined
(.hello :flash :text "PUSH ME")
so here the same keyword :text
would mean something else, eg
to flash a subliminal message on the screen.
We often refer to calls to the widget functions as messages. One reason for this is that they actually turn into messages to the graphics process `gcltksrv'. To actually see these messages you can do
(debugging t).
On successful completion, the widget constructor functions return the
symbol passed in as the first argument. It will now have a functional
binding. It is an error to pass in a symbol which already corresponds
to a widget, without first calling the destroy
command. On failure,
an error is signalled.
The widget functions themselves, do not normally return any value. Indeed the lisp process does not wait for them to return, but merely dispatches the commands, such as to change the text in themselves. Sometimes however you either wish to wait, in order to synchronize, or you wish to see if your command fails or succeeds. You request values by passing the keyword :return and a value indicating the type.
(.hello :configure :text "Bye World" :return 'string) ==> "" ==> T
the empty string is returned as first value, and the second value
T
indicates that the new text value was successfully set. LISP
will not continue until the tkclsrv process indicates back that the
function call has succeeded. While waiting of course LISP will continue
to process other graphics events which arrive, since otherwise a
deadlock would arise: the user for instance might click on a mouse, just after
we had decided to wait for a return value from the .hello
function.
More generally a user program may be running in GCL and be interrupted
to receive and act on communications from the `gcltksrv'
process. If an error occurred then the second return value of the
lisp function will be NIL. In this case the first value, the string
is usually an informative message about the type of error.
A special variable tk::*break-on-errors*
which if not
nil
, requests that that LISP signal an error when a message
is received indicating a function failed. Whenever a command fails,
whether a return value was requested or not, `gcltksrv' returns a
message indicating failure. The default is to not go into the
debugger. When debugging your windows it may be convenient however to
set this variable to T
to track down incorrect messages.
The `gcltksrv' process always returns strings as values.
If :return
type is specified, then conversion to type
is accomplished by calling
(coerce-result return-string type)
Here type must be a symbol with a coercion-functions
property.
The builtin return types which may be requested are:
T
number
list-strings
(coerce-result "a b {c d} e" 'list-strings) ==> ("a" "b" "c d" "e")
boolean
The above symbols are in the TK
or LISP
package.
It would be possible to add new types just as the :return t
is done:
(setf (get 't 'coercion-functions) (cons #'(lambda (x) (our-read-from-string x 0)) #'(lambda (x) (format nil "~s" x))))
The coercion-functions
property of a symbol, is a cons whose
car
is the coercion form from a string to some possibly different
lisp object, and whose cdr
is a function which builds a string
to send to the graphics server. Often the two functions are inverse
functions one of the other up to equal.
The control funcions (see section Control) do not return a value
or wait unless requested to do so, using the :return
keyword.
The types and method of specification are the same as for the
Widget Functions in the previous section.
(winfo :width '.hello :return 'number) ==> 120
indicates that the .hello
button is actually 120 pixels
wide.
The rule is that the first argument for a widget function is a keyword, called the option. The pattern of the remaining arguments depends completely on the option argument. Thus
(.hello option ?arg1? ?arg2? ...)
One option which is permitted for every widget function is
:configure
. The argument pattern following it is the same
keyword/value pair list which is used in widget creation. For a
button
widget, the other valid options are :deactivate
,
:flash
, and :invoke
. To find these, since
.hello
was constructed with the button
constructor, you
should see See section button.
The argument pattern for other options depends completely on the option
and the widget function.
For example if .scrollbar
is a scroll bar window, then the option
:set
must be followed by 4 numeric arguments, which indicate how
the scrollbar should be displayed, see See section scrollbar.
(.scrollbar :set a1 a2 a3 a4)
If on the other hand .scale
is a scale (see section scale), then we have
(.scale :set a1 )
only one numeric argument should be supplied, in order to position the scale.
These are
(widget-constructor pathname :keyword1 value1 :keyword2 value2 ...)
to create the widget whose name is pathname. The possible keywords allowed are specified in the corresponding section of See section Widgets.
What has been said so far about arguments is not quite true. A special string concatenation construction is allowed in argument lists for widgets, widget constructors and control functions.
First we introduce the function tk-conc
which takes an arbitrary
number of arguments, which may be symbols, strings or numbers, and
concatenates these into a string. The print names of symbols are
converted to lower case, and package names are ignored.
(tk-conc "a" 1 :b 'cd "e") ==> "a1bcde"
One could use tk-conc
to construct arguments for widget
functions. But even though tk-conc
has been made quite
efficient, it still would involve the creation of a string. The
:
construct avoids this. In a call to a widget function,
a widget constructor, or a control function you may remove the call to
tk-conc
and place :
in between each of its arguments.
Those functions are able to understand this and treat the extra
arguments as if they were glued together in one string, but without
the extra cost of actually forming that string.
(tk-conc a b c .. w) <==> a : b : c : ... w (setq i 10) (.hello :configure :text i : " pies") (.hello :configure :text (tk-conc i " pies")) (.hello :configure :text (format nil "~a pies" i))
The last three examples would all result in the text string being
"10 pies"
, but the first method is the most efficient.
That call will be made with no string or cons creation. The
GC Monitor example, is written in such a way that there is no
creation of cons
or string
types during normal operation.
This is particularly useful in that case, since one is trying to
monitor usage of conses by other programs, not its own usage.
It is possible to make certain areas of a window mouse sensitive, or to run commands on reception of certain events such as keystrokes, while the focus is in a certain window. This is done by having a lisp function invoked or some lisp form evaluated. We shall refer to such a lisp function or form as a command.
For example
(button '.button :text "Hello" :command '(print "hi")) (button '.jim :text "Call Jim" :command 'call-jim)
In the first case when the window .button
is clicked on, the
word "hi" will be printed in the lisp to standard output. In the
second case call-jim
will be funcalled with no arguments.
A command must be one of the following three types. What happens depends on which type it is:
functionp
then it will be called with
a number of arguments which is dependent on the way it was bound,
to graphics.
The following keywords accept as their value a command:
:command :yscroll :yscrollcommand :xscroll :xscrollcommand :scrollcommand :bind
and in addition bind
takes a command as its third argument,
see See section bind.
Below we give three different examples using the 3 possibilities for
a command: functionp, string, and lisp form. They all accomplish
exactly the same thing.
For given a frame .frame
we could construct a listbox
in it as:
(listbox '.frame.listbox :yscroll 'joe)
Then whenever the listbox view position changes, or text is inserted,
so that something changes, the function joe
will be invoked with 4
arguments giving the totalsize of the text, maximum number of units
the window can display, the index of the top unit, and finally the
index of the bottom unit. What these arguments are is specific
to the widget listbox
and is documented See section listbox.
joe
might be used to do anything, but a common usage is to have
joe
alter the position of some other window, such as a scroll
bar window. Indeed if .scrollbar
is a scrollbar then
the function
(defun joe (a b c d) (.scrollbar :set a b c d))
would look after sizing the scrollbar appropriately for the percentage of the window visible, and positioning it.
A second method of accomplishing this identical, using a string (the second type of command),
(listbox '.frame.listbox :yscroll ".scrollbar set")
and this will not involve a call back to lisp. It uses the fact that
the TK graphics side understands the window name .scrollbar
and
that it takes the option set
. Note that it does not get
the :
before the keyword in this case.
In the case of a command which is a lisp form but is not installed
via bind
or :bind
, then the form will be installed as
#'(lambda (&rest *arglist*) lisp-form)
where the lisp-form might wish to access the elements of the special
variable *arglist*
. Most often this list will be empty, but for
example if the command was setup for .scale
which is a scale,
then the command will be supplied one argument which is the new numeric
value which is the scale position. A third way of accomplishing the
scrollbar setting using a lisp form is:
(listbox '.frame.listbox :yscroll '(apply '.scrollbar :set *arglist*))
The bind
command and :bind
keyword, have an additional
wrinkle, see See section bind. These are associated to an event in a
particular window, and the lisp function or form to be evaled must have
access to that information. For example the x y position, the window
name, the key pressed, etc. This is done via percent symbols which
are specified, see See section bind.
(bind "Entry" "<Control-KeyPress>" '(emacs-move %W %A ))
will cause the function emacs-move to be be invoked whenever a control key is pressed (unless there are more key specific or window specific bindings of said key). It will be invoked with two arguments, the first %W indicating the window in which it was invoked, and the second being a string which is the ascii keysym which was pressed at the same time as the control key.
These percent constructs are only permitted in commands which are
invoked via bind
or :bind
. The lisp form which is passed
as the command, is searched for the percent constructs, and then a
function
#'(lambda (%W %A) (emacs-move %W %A))
will be invoked with two arguments, which will be supplied by the
TK graphics server, at the time the command is invoked. The
*arglist*
construct is not available for these commands.
It is possible to link lisp variables to TK variables. In general
when the TK variable is changed, by for instance clicking on a
radiobutton, the linked lisp variable will be changed. Conversely
changing the lisp variable will be noticed by the TK graphics side, if
one does the assignment in lisp using setk
instead of
setq
.
(button '.hello :textvariable '*message* :text "hi there") (pack '.hello)
This causes linking of the global variable *message*
in lisp
to a corresponding variable in TK. Moreover the message that is in
the button .hello
will be whatever the value of this global
variable is (so long as the TK side is notified of the change!).
Thus if one does
(setk *message* "good bye")
then the button will change to have good bye as its text.
The lisp macro setk
expands into
(prog1 (setf *message* "good bye") (notice-text-variables))
which does the assignment, and then goes thru the linked variables
checking for those that have changed, and updating the TK side should
there be any. Thus if you have a more complex program which might
have done the assignment of your global variable, you may include
the call to notice-text-variables
at the end, to assure that
the graphics side knows about the changes.
A variable which is linked using the keyword :textvariable
is
always a variable containing a string.
However it is possible to have other types of variables.
(checkbutton '.checkbutton1 :text "A button" :variable '(boolean *joe*)) (checkbutton '.checkbutton2 :text "A button" :variable '*joe*) (checkbutton '.checkbutton3 :text "Debugging" :variable '(t *debug*) :onvalue 100 :offvalue -1)
The first two examples are the same in that the default variable type
for a checkbutton is boolean
. Notice that the specification of a
variable type is by (type variable)
. The types which are
permissible are those which have coercion-fucntions, See section Return Values. In the first example a variable *joe*
will be linked, and
its default initial value will be set to nil, since the default initial
state of the check button is off, and the default off value is nil.
Actually on the TK side, the corresponding boolean values are "1"
and "0"
, but the boolean
type makes these become t
and nil
.
In the third example the variable *debug* may have any lisp value (here
type is t
). The initial value will be made to be -1
,
since the checkbutton is off. Clicking on .checkbutton3
will
result in the value of *debug*
being changed to 100, and the light
in the button will be toggled to on, See section checkbutton. You may
set the variable to be another value besides 100.
You may also call
(link-text-variable '*joe* 'boolean)
to cause the linking of a variable named *joe*. This is done automatically whenever the variable is specified after one of the keys
:variable :textvariable.
Just as one must be cautious about using global variables in lisp, one
must be cautious in making such linked variables. In particular note
that the TK side, uses variables for various purposes. If you make a
checkbutton with pathname .a.b.c
then unless you specify a
:variable
option, the variable c
will become associated to
the TK value of the checkbutton. We do NOT link this variable by
default, feeling that one might inadvertently alter global variables,
and that they would not typically use the lisp convention of being of
the form *c*
. You must specify the :variable
option, or
call link-variable
.
tkconnect &key host display can-rsh gcltksrv
This function provides a connection to a graphics server process, which
in turn connects to possibly several graphics display screens. The
graphics server process, called `gcltksrv' may or may not run
on the same machine as the lisp to which it is attached.
display
indicates the name of the default display to connect to, and this
in turn defaults to the value of the environment variable DISPLAY
.
When tkconnect is invoked, a socket is opened and it waits for
a graphics process to connect to it. If the host argument is not
supplied, then a process will be spawned which will connect back to
the lisp process. The name of the command for invoking the process
is the value of the `gcltksrv' argument, which defaults to
the value of the environment variable GCL_TK_SERVER
. If that variable
is not set, then the lisp *lib-directory*
is searched for
an entry `gcl-tk/gcltksrv'.
If host
is supplied, then a command to run on the remote machine
will be printed on standard output. If can-rsh
is not nil,
then the command will not be printed, but rather an attempt will be
made to rsh to the machine, and to run the command.
Thus
(tkconnect)
would start the process on the local machine, and use for display
the value of the environment variable DISPLAY
.
(tkconnect :host "max.ma.utexas.edu" :can-rsh t)
would cause an attempt to rsh to max
and to run the command
there, to connect back to the appropriate port on the localhost.
You may indicate that different toplevel windows be on different
displays, by using the :display
argument when creating the
window, See section toplevel.
Clearly you must have a copy of the program `gcltksrv' and TK libraries installed on the machine where you wish to run the server.
Go to the first, previous, next, last section, table of contents.