CL-TK provides a very basic bridge to Tcl/Tk. It will fire up a Tk window, handle events, and allow you to evaluate Tcl code.
25-11-2011: Version 0.2: Fixes several small issues in escaping and backend spawning.
16-03-2009: Version 0.1: The first public release of this library.
This library is released under a zlib-style license, which approximately means you can use the code in whatever way you like, except for passing it off as your own or releasing a modified version without indication that it is not the original. See the LICENSE file in the distribution.
CL-TK requires Tcl/Tk 8.5 to be
installed. It also has an optional dependence on CFFI (on platforms
other than Allegro Common Lisp). Not having CFFI installed means
the FFI backend is not available, and you have to use the
wish
based backend, which is somewhat slower.
The current release of CL-TK can be downloaded from http://marijnhaverbeke.nl/cl-tk/cl-tk.tgz, or installed with asdf-install.
A git repository with the most recent changes can be checked out with:
> git clone http://marijnhaverbeke.nl/git/cl-tk
The code is also available on github.
There is currently no mailing list, so mail me directly. I'll set something up when there is enough traffic.
To use CL-TK, a basic understanding of Tcl and Tk is indispensable. If you are completely new to those, you might want to read up on them first.
When working with Tk, the *tk*
special variable
has to be bound to a back-end object. The simplest way to do this
is:
(toplevel-tk)
This will automatically create a suitable Tk back-end. See below for details on what these back-ends are.
You should now see an empty window. Next, we'll want to put something into this window:
(tcl "pack [ttk::label .label -text {Hello world}]")
Or, if you don't want to pass the whole command as one big string:
(tcl "pack" (tcl[ "ttk::label" ".label" :text "Hello world"))
See the reference for information about the
way in which arguments to tcl
and
tcl[
are treated.
Depending on your platform and back-end, it's possible that you
don't see your label in the window yet. That's because it is not
reacting to events. mainloop
will run Tk's event pump until the window is closed.
(mainloop)
Another capability that this library provides is running Lisp code when Tk events occur. For example:
(with-tk () (tcl "pack" (tcl[ "ttk::button" ".exit" :text "Exit" :command (event-handler #'destroy))) (mainloop))
with-tk
locally binds
*tk*
. The destroy
function (predictably) destroys the Tk instance. But what does event-handler
do? Tk event
handlers are pieces of Tcl code, not Lisp functions.
event-handler
returns a piece of code that, when
executed, will notify the CL-TK event pump, which will call the
function that was passed to event-handler
. More about
this below.
There are two types of back-ends. It is never obligatory to
specify a back-end, and when you don't the sensible thing happens:
The system tries to use the faster FFI back-end, and falls back on
wish
when that fails.
class ffi-tk
Only available when you either have CFFI installed, or are using Allegro Common Lisp. A back-end that tries to load the Tcl and Tk shared libraries, and talk to them through a foreign-function interface.
class wish-tk
This back-end starts a wish
(windowing shell) process, and communicates with that through its
standard in and output channels. You can specify a
:binary
initarg to specify the name (and optionally
path) of the executable to run. This can be a single string or
pathname, or a list of strings and pathnames.
variable *tk*
Holds the 'current' back-end. This is used by most
of the function in the library. Bind with toplevel-tk
and with-tk
.
function toplevel-tk (&optional back-end)
Set *tk*
to a
back-end. When no argument is given, the library tries to create a
suitable back-end.
macro with-tk ((&optional back-end) &body body)
Execute body
with *tk*
bound to a back-end. Wraps an
unwind-protect
that makes sure the back-end is
destroyed when the body exits.
function destroy ()
Destroy the current back-end. This will close the top-level window, and dispose of any resources that were held.
function alive-p ()
Test whether the current back-end is still alive.
After the user manually closes or kills the top-level window, this
will return nil
.
The main thing to do with this library is sending Tcl commands to an interpreter, in order to make some windowing interface do what you want. The following commands try to make this easy.
function tcl (command &rest args)
Run a piece of Tcl code in the current back-end. The first argument is sent as-is, the rest are converted to strings by the rules below, separated by spaces, and appended to the first argument.
princ-to-string
.lit
function. They are inserted
into the command as they are, without any escaping.So, for example, the following packs the
.foo.scroll
widget into the right side of its
container.
(tcl "pack" ".foo.scroll" :side "right" :expand 1 :fill "y")
The return value of tcl
is the string
produced by evaluating the given expression. If Tcl signals an
error, a tcl-error
condition
is raised.
function lit (string)
Wraps the given string to be inserted literally into a Tcl command.
function tcl[ (command &rest args)
Like tcl
, but
instead of sending the result to the back-end, it is wrapped in
'[' and ']' and returned as a literal, so that it can be inserted
into another command. For example...
(tcl "grid" (tcl[ "ttk::frame" ".baz" :padding 10) :column 0 :row 1 :sticky "news")
function tcl{ (command &rest args)
Just like tcl[
, but wraps its result in
'{' and '}'.
function tcl-escape (string)
Escape a string so that it can be passed as an
argument to a Tcl operator. Disables all $
and
[ ]
magic by adding backslashes.
A Tcl application, or at least its interaction with Lisp (depending on your back-end) only runs when its event pump is running.
function doevent (&optional block)
Handle one event. When block
is true,
this will wait until an event becomes available, otherwise it will
return immediately. Returns a boolean indicating whether an event
was handled (always true in blocking mode).
function doevents ()
Handle events (without blocking) until no more events are available.
function mainloop ()
Handle events (blocking) until the current back-end is no longer alive.
CL-TK allows you to associate 'magic' Tcl commands with pieces of Lisp code. These commands are then registered as Tcl event handlers, and the CL-TK message pump detects when they are fired, running your Lisp code.
function event-handler (function &optional fields)
Return a string containing a Tcl command
associated with calling the given function. When
fields
is given (usually you don't need it), it
should be a list of characters, which will be put into the Tcl
command as %c
markers (see bind
),
with their substitutions passed as arguments to the function.
Returns the handler ID (see below) as a second value. For
example:
(tcl "ttk:button" :text "Exit" :command (event-handler #'destroy))
macro event-handler* (&body body)
Wraps the given body
in a lambda, and
passes it to event-handler
.
macro bind-event (tag event (&rest fields) &body body)
Binds the
given event for the given tag to a function containing the macro
body. If fields
is given, it should hold
(variable #\c)
pairs, where the second element is the
character to put into the Tcl event handler, and the first element
is the variable to which the substitution of this character will
be bound in the body. Returns an event ID.
(bind-event "." "<1>" ((x #\x) (y #\y)) (format t "You clicked at ~a,~a.~%" x y))
The tricky thing with associating Lisp code with Tcl strings is that the two systems have separate garbage collectors, and as such it is unclear how long the Lisp closures should live. In toy applications, or applications that don't dynamically register events, you can ignore this fact. But usually, you should take care to clean up your handlers, or you are leaking memory.
function unregister-event (id)
Release the closure stored in the current back-end under the given event ID.
function event-snapshot ()
Retrieve a 'snapshot' of the current event handler
set. At a later time, you can use clear-events
to release all
event handlers registered after this snapshot was taken.
function clear-events (snapshot)
Release all events registered since the given snapshot.
macro with-local-events (&body body)
Register an event snapshot before running the macro body,
and unwind-protect
the body to release that snapshot
when it unwinds.
Tk requires you to come up with a unique 'window name' (as in .main.foo.label20) for every widget you create. This gets pretty tiresome, but these functions try to ease the pain a little.
variable *wname*
If you decide to use CL-TK's wname facilities, you'll use this to store the window name of the widget you're currently populating.
macro with-wname (wname &body body)
Binds *wname*
to the given value in body
.
function wname (name &optional id)
Create a new window name, based on *wname*
. If *wname*
is .foo
, (wname "bar")
gives you
.foo.bar
, and (wname "bar" 10)
gives
.foo.bar10
.
function wname-cons (name base)
Extend a base window name. For example,
(wname-cons "button" ".frame")
yields
".frame.button"
.
function wname-car (wname)
Take the inner element of a window name.
function wname-cdr (wname)
Return the outer element of window name. For
example (wname-cdr ".dialog.top.scroll")
gives
.dialog.top
.
All errors raised by CL-TK have the type
tcl-error
, which is a subclass of
simple-error
without extra slots.