clTcl Manual

Home Installation Manual Examples Specification

Contents

GUI programming

The most important functions for GUI programming are

and less frequently used

Embedding scripts

Scripts are embedded with the #TCL[...] syntax. At the dots any literal Tcl script can occur. For example

(cltcl:event-loop
  #TCL[message .m -text "Your message here"
       button .b -text "Close" -command exit
       pack .m
       pack .b])
      

A script is read as a quoted list of strings with each string a command from the script. The previous example could also have been written as

(let ((script #TCL[message .m -text "Your message here"
 	           button .b -text "Close" -command exit
   	           pack .m
	           pack .b]))
  (cltcl:event-loop script))
      
or even as
(let ((partA #TCL[message .m -text "Your message here"
	          button .b -text "Close" -command exit])
      (partB #TCL[pack .m
	          pack .b]))
  (cltcl:event-loop (append partA partB)))
      
The next repl interaction shows how #TCL reads a script
CL-USER> (dolist (x #TCL[message .m -text "Your message here here"
                         button .b -text "Close" -command exit 
                         pack .m
                         pack .b])
           (print x))
"message .m -text \"Your message here\""
"button .b -text \"Close\" -command exit"
"pack .m"
"pack .b"
NIL
CL-USER>
	

Setting up an application

Use function event-loop to set up an event loop for your application. It starts the Tcl/Tk interpreter, executes the given script and starts listening for events to dispatch.

A typical application event loop might look like the following fragment

(let ((script #TCL[proc main {} {
                     wm title . "Your appname here"

                     # Add a menu bar
                     menu .mbar
                     . config -menu .mbar
                 
                     # Add a file menu
                     menu .mbar.file -tearoff 0
                     .mbar add cascade -label "File" -underline 0 -menu .mbar.file
                     .mbar.file add command \
                       -label "Open..." \
                       -accelerator "Ctrl+O" \
                       -underline 0 \
                       -command {cltcl::callLisp on-file-open}
                     .mbar.file add separator
                     .mbar.file add command -label "Exit..." -command exit
                     # etc
                   
                     # Widgets
                     text .t 
                     # etc
                   
                     # Let a window manager display the widgets.
                     grid .t -sticky nsew
                     grid rowconfigure . 0 -weight 1
                     grid columnconfigure . 0 -weight 1
                   
                     #etc
                   }

                   main]))
  (handler-bind 
    ((condition (lambda (error)
                  (ignore-errors
                    (cltcl:call "tk_messageBox"
                                :message (format nil "Error:~2%~A" error)))
		  (cltcl:keep-listening))))
   (cltcl:event-loop script)))
      
The Tcl/Tk script defines proc main and ends with a call to it (it is usually a good idea to define a proc to avoid polluting Tcl/Tk's namespace for variables). The script is run by event-loop. The condition handlers catches errors that occur in event handlers. Restart keep-listening prevents abortion of the even loop.

During development you might want to remove the condition handler and let the debugger catch the error. In that case the debugger shows the restart as an option.

During the event loop's lifetime variable *stream* is bound to the stream that connects to the interpreter to make it accessable from event handlers. This stream is used by functions call, post and run.

The event loop also binds variable *interpreter* to the interpreter that was given to make it the default for nested event loops. Therefore you have to give the path to the interpreter only once at the top level of you application. This variable's value is the default for the :interpreter keyword in other functions

Event handlers

User events in Tcl/Tk are handled by scripts that are bound to a widget via the bind command or via the -command option. Use function cltcl::callLisp in a Tck/Tk event to make a call back to Lisp. The event loop listener calls the Lisp event handler. For example

(cltcl:event-loop
  #TCL[set counter 0
       button .b -text "Click me and watch your repl" -command {
         cltcl::callLisp print $counter
         set counter [expr $counter+1]
       }
       button .c -text Close -command exit
       pack .b
       pack .c])
      
When the button is clicked a call is make to Lisp's print function.

Function cltcl::callLisp expects a command and a number of arguments:

cltcl::callLisp function ?arg0 ?arg1 ...?
      
The listener applies the Lisp function to the arguments, sends the result back to Tcl/Tk as a properly escaped string, and resumes listening.

Calling and posting commands

Use function call to call a Tcl/Tk command from an event handler. For example a handler on-file-open for the example application top loop could start as follows:

(defun on-file-open ()
  "Called when the user selects the 'File Open menu."
  (let ((file (cltcl:call "tk_getOpenFile"
                          :defaultextension ".lisp"
                          :filetypes '(("Lisp" ".lisp")
                                       ("All files" ".*")))))
    ...))
      
The call corresponds to Tcl/Tk call
tk_getOpenFile -defaultextension ".lisp" \
  -filetypes {{"Lisp" ".lisp"} {"All files" ".*"}}
      

Function call expects a command and a number of arguments and lets Tcl/Tk interpret the command with all arguments properly escaped. For convenience keyword symbols are written with a hyphen prefixed, the Tcl convention for options. All other values are written to string and escaped.

Function post is similar to function call except it doesn't wait for a reply. Since this doesn't disrupt the event loop's listener this can for example be useful to update the gui from a seperate thread.

Calling commands with function call or post is a good way to exchange information with Tcl/TK. It takes care of all necessary string escaping. If you inject them in a script with function format-script you have to take care of the escaping yourself.

Running scripts

Run scripts from an event handler with function run. The arguments are bound to Tcl variable argv. This variable normally contains a Tcl script's command line arguments.

Although the arguments of run are properly escaped, often it is more convenient to simply wrap the script in a Tcl proc. The script containing this proc needs to be run only once and the proc can be called safely with function call or post.

Handling Tcl data

Any peace of Tcl data is a string so that is easy. The only difficult part is handling Tcl lists.

Tcl has a peculiar way of writing lists but as long as you know the list structure this is no problem. A list is written as a whitespace seperated string. The problem is that not every whitespace seperated list was necessarily intended to be a list. For that reason there are no inverse conversion functions between Tcl lists and Lisp lists.

Use function write-list-to-Tcl-string to write a Lisp list to a Tcl string. For example

CL-USER> (cltcl:write-list-to-Tcl-string
           (list "foo" "foo bar" (list "foo" "foo bar")))
"foo foo\\ bar {foo foo\\ bar}"
      
All list items are properly escaped and nested lists are written recursively.

Use function read-tcl-list-from-string1 to read a Tcl list from a string. Continuing from the previous example

CL-USER> (cltcl:read-tcl-list-from-string1 *)
("foo" "foo bar" "foo foo\\ bar") 
CL-USER> (cltcl:read-tcl-list-from-string1 (third *))
 ("foo" "foo bar")
      
Nested lists are left intact, just the top level of the list is read.

The next example show why reading is not recursive. The string "foo bar" would be seen as a list.

CL-USER> (cltcl:read-tcl-list-from-string1 (second *))
("foo" "bar")     
      
You have make the proper calls depending on the contents of a list.

Properly escaped strings

Functions call, run and write-list-to-Tcl-string escape their arguments and are always safe to use. For other functions you have to ensure yourself that values are properly escaped.

Use function escape to escape strings. String that are not properly escaped might contain Tcl commands that could be evaluated unintenionally.

The repl

Function repl start a read-eval-print loop. Be careful not to send commands that write output because that will be picked up by the listener. You might want to run the event loop in a seperate thread to have access to the Lisp repl. Then you can start the clTcl repl from there.

Lower level functions

Functions for reading and manipulation Tcl/Tk scripts and data are

Functions to connect to a Tcl/Tk interpreter are

Injecting values into a script

Use function format-script to inject Lisp values into a Tcl script. All formatter commands can be used but usually ~A is sufficient. Use [list ~A] for lists, as in the following example.

(cltcl:event-loop
  (cltcl:format-script
    #TCL[set l [list ~A]
         tk_messageBox -message ~A
         button .b -text ~A -command {
           foreach x $l {
             tk_messageBox -message $x
           }
           exit
         }
         pack .b]
    (cltcl:write-list-to-Tcl-string 
      (list "[expr 1 + 1]" "[list foo bar]"))
    "[expr 1 + 1]"
    (cltcl:escape "[exit]")))
      
All strings contain Tcl command, but only one of them gets evaluated, the "[expr 1 + 1]" that is not escaped.

Be careful and always use escape to prevent code injection.

Working with Tcl/Tk streams

See the specification for with-Tcl/Tk, open-Tcl/Tk-stream, close-Tcl/Tk-stream, send-script and receive-line. These functions hide the implementation dependent details behind an abstraction layer.

Reading Tcl

See the specification for functions read-script, read-list and read-word. Tcl's syntax rules are described here.