
Q-Tk
====

This module provides a basic interface between Q and Tcl/Tk. The operations of
this module allow you to execute arbitrary commands in the Tcl interpreter,
set and retrieve variable values in the interpreter, and invoke Q callbacks
from Tcl/Tk.

A recent version of Tcl/Tk is required (8.0 or later should do). You can get
this from http://www.tcl.tk. Both releases in source form and binary releases
for Windows and various Unix systems are provided there.

The "stub" script is in tk.q, tk.c contains the actual C code for the
module. A description of the available functions can be found in tk.q. A very
basic example can be found in tk_test.q; a slightly more advanced example of a
tiny but complete Q-Tk application is in tk_examp.q. For a really substantial
Q-Tk program take a look at the graphed script in the Q-Graph package,
available from the Q homepage.

*** IMPORTANT ***

When starting a new interpreter, the Tcl/Tk initialization code looks for some
initialization files which it executes before anything else happens. Usually
these files will be found without any further ado, but if that does not happen
automatically, you must set the TCL_LIBRARY and TK_LIBRARY environment
variables to point to the Tcl and Tk library directories on your system. For
instance, using Tcl/Tk 8.4 under Unix, you might set these variables as
follows (assuming Tcl/Tk is installed under /usr):

(sh:)  export TCL_LIBRARY=/usr/lib/tcl8.4; export TK_LIBRARY=/usr/lib/tk8.4
(csh:) setenv TCL_LIBRARY /usr/lib/tcl8.4; setenv TK_LIBRARY /usr/lib/tk8.4

Under Windows, with Tcl/Tk installed under c:/tcl (these settings are also
taken care of automagically by the Qpad application):

set TCL_LIBRARY=c:/tcl/lib/tcl8.4
set TK_LIBRARY=c:/tcl/lib/tk8.4

Similar initializations may be required when using optional Tcl/Tk extension
packages such as Tix (http://tix.mne.com).


USAGE
=====

You can submit a command to the Tcl/Tk interpreter with `tk CMD' where CMD is
a string. This also starts a new instance of the Tcl/Tk interpreter if it is
not already running. To stop the Tcl/Tk interpreter, you can use the `tk_quit'
function. Interpreters are local to the thread in which they are started. Thus
in a multithreaded script you can have multiple interpreters running in
different threads. (For this to work, your Tcl/Tk version must have been built
with thread support. Otherwise only one interpreter in the main thread is
allowed.)

Simple dialogs can be created directly using Tk's `tk_messageBox' and
`tk_dialog' functions. For instance:

tk "tk_dialog .warning \"Warning\" \"Are you sure?\" warning 0 Yes No Cancel"

Other kinds of common dialogs are available; see the Tcl/Tk manual for
information.

For more elaborate applications you probably have to explicitly create some
widgets and provide a main loop which takes care of events and callbacks. For
this purpose, the Tcl command `q' can be used to return a "callback message"
from the Tcl interpreter to the calling Q script. The Q-Tk module maintains
such messages in a private queue. You can access callback messages in FIFO
fashion, using the `tk_reads' function which returns the oldest message as a
string. There are also two convenience functions, tk_read = val tk_reads, and
tk_readq = valq tk_reads, which are useful when the callback messages denote Q
expressions which are to be evaluated immediately or returned as a quoted
expression, respectively. All these functions, as well as the `tk_check' and
`tk_ready' functions discussed below, also process pending events in the
Tcl/Tk interpreter s.t. the display is updated when Tk commands or user
actions change the state of the application.

The Tcl/Tk interpreter keeps running until either `tk_quit' is called, or the
main window is destroyed or closed. You can check whether the interpreter is
currently running using the `tk_ready' function. A basic main loop, which
repeatedly evaluates callback messages and processes events until the
interpreter is exited, may look as follows:

main_loop		= tk_read || main_loop if tk_ready;
			= () otherwise;

This is appropriate if the whole state of the application is kept in the Tcl
interpreter. Otherwise you might wish to invoke the loop with some initial
STATE value on which the callback functions operate. In this case you can
change the definition of main_loop to something like the following:

main_loop STATE		= main_loop (tk_read STATE) if tk_ready;
			= STATE otherwise;

In any case, the loop terminates as soon as the Tcl/Tk interpreter is exited,
which can happen, e.g., in response to a callback which invokes the `tk_quit'
function, Tcl code which destroys the main window (`destroy .'), or when the
user closes the main window from the window manager.

Note that the `tk_read' functions are blocking, i.e., they wait until a
callback message becomes available. Sometimes you will also want to check
beforehand whether there are any messages, which can be done with the
`tk_check' function. This is useful, e.g., to implement any kind of background
processing when the application is currently idle:

main_loop STATE		= STATE if not tk_ready;
			= main_loop (tk_read STATE) if tk_check;
			= main_loop (idle STATE) otherwise;

Alternatively, you can also do background processing using the Tcl
interpreter's own facilities, i.e., the Tcl `after' command.

Q-Tk also allows your script to set and retrieve variable values in the Tcl
interpreter with the tk_set and tk_get functions. This is useful, e.g., to
change the variables associated with entry and button widgets, and to retrieve
the current values from the application. Note that all values are passed as
strings, since this is really the only data type Tcl/Tk knows.


TIPS AND TRICKS
==== === ======

(1) Errors in Tcl/Tk commands can be handled by giving an appropriate
definition of the `tk_error' function, which is invoked with an error message
as its single argument. For instance, the following implementation of
`tk_error' prints the error message and halts evaluation:

tk_error MSG		= writes ("! Tk Error: "++MSG++"\n") || halt;

If no definition for this function is provided, then errors cause a literal
`tk_error MSG' expression to be returned as the result of the `tk'
function. You can then check for such results to take an appropriate action.

(2) The Tcl/Tk interpreter, when started, displays a default main window,
which is required by most Tk applications. If this is not desired (e.g., if
only the basic Tcl commands are needed), you can hide this window using a `tk
"wm withdraw ."' command. To redisplay the window when it is needed, use the
`tk "wm deiconify ."' command. It is also common practice to use `wm withdraw'
and `wm deiconify' while creating the widgets of an application, in order to
reduce "flickering".

(3) The `tk' function can become rather tedious when coding larger Tk
applications. Usually, you will prefer to put the commands making up your
application into a separate Tcl/Tk script, and then invoke this script from
your Q script using the Tcl `source' command, e.g.:

==> tk "source myapp.tcl"

This is also the method to use for running existing Tk applications, e.g., if
you create the interface using some interface builder like vtcl
(http://vtcl.sourceforge.net).

(4) The Tcl `package' command allows you to load additional extensions into
the Tcl/Tk interpreter at runtime. For instance, if you want to work with the
extra widgets provided by the Tix extension (see http://tix.mne.com), you can
load the corresponding package as follows:

==> tk "package require Tix"

(5) The Tcl `exit' procedure, just as in tclsh or wish, causes exit from the
the current process. Since the Tcl/Tk interpreter hosted by the Q-Tk module
runs as part of the Q interpreter process, and not as a separate child
process, `tk "exit"' will exit from the Q interpreter and take you back to the
shell. If you'd like `exit' to only exit the Tcl/Tk interpreter, without
exiting Q, you can redefine the `exit' procedure, e.g., as follows:

==> tk "proc exit { {returnCode 0} } { q tk_quit }"

(Of course, the `tk_quit' call will only take effect next time you use
`tk_read', but presumably that will be done by your application's main loop.)

If you want to do something with the exit code provided by `exit', you will
have to provide an appropriate callback function, e.g.:

==> tk "proc exit { {returnCode 0} } { q quit_cb $returnCode }"

An implementation of `quit_cb' might then look as follows:

quit_cb 0  = writes "Application exited normally.\n" || tk_quit;
quit_cb N  = writes ("Application exited with exit code "++str N++".\n") ||
             tk_quit otherwise;

(6) If you need dialogs beyond the standard kinds of message boxes and common
dialogs, you will have to do these yourself using a secondary toplevel. The
dialog toplevel is just like the main window but will only be shown when the
application needs it. You can construct both non-modal and modal dialogs this
way, the latter can be implemented using Tk's `grab' command. An example can
be found in the Q-Graph package on the Q homepage.

(7) The latest version of this module also supports synchronous callbacks from
the Tcl interpreter via the special `qval' command. This is the recommended
method when the main loop of the application is actually executed in the Tcl
interpreter, as is the case for some Tcl extensions like Gnocl
(http://www.dr-baum.net/gnocl/). The arguments of `qval' take the same form as
with the `q' command described above. They should form a valid Q expression
which is parsed and evaluated in the Q interpreter. Such callbacks may also
return a string value to the Tcl interpreter. There also is a second new Tcl
command, `qtrace', which works like `qval' but also prints messages as the
evaluation of the callback starts and end, which is _very_ useful for
debugging purposes. ;-) The `qtrace' command also accepts a Tcl boolean value
(any of 0, 1, true, false, yes, no, on, off are recognized) which causes it to
switch on or off the automatic tracing of all calls to the qval command.


Well, I hope this suffices to get you started. Please also see tk_examp.q for
a tiny, but complete example Tk application. Furthermore, the tix.q example
illustrates some of the additional bits of Tk code discussed above, including
the method to run a Tix script with Q-Tk.


Enjoy! :)

January 23 2006
Albert Graef
ag@muwiinfa.geschichte.uni-mainz.de, Dr.Graef@t-online.de
http://www.musikwissenschaft.uni-mainz.de/~ag
