Next Contents

User's Manual

This manual is a simple tutorial for CamlTk . When writing your own programs, you should consult the reference manual (chapter 3), as well as the Tk man pages. Whenever the reference manual is unsufficient, you can check the Widgets.src file (which syntax is found in chapter 2) since this file gives the mappings between Tk and Caml Light for functions and data.

Modules

The CamlTk interface is provided as a Caml Light library, composed of the following modules:

All modules required by the CamlTk interface are in the tklib.zo library archive.

The organization of the interface is designed so that, in general, you should #open only the tk module, and for the rest of the functions use the modulename__symbol syntax (e.g. text__insert). This convention is preferred because some functions (such as create and configure) exist in many different modules. In this context, referring to functions with exact qualified names makes programs clearer and less error-prone.

Basics

As usual with toolkits, the simplest way to learn how to write programs is by copy-paste-ing from available source code. In general, a Caml Light program using the Tk toolkit will have the following structure :

#open "tk";;            (* Make interface functions available *)
let top = openTk ();;   (* Initialisation of the interface *)
(* top is now the toplevel widget *)

(* Widget initialisation *)
let b = button__create top 
          [Text "foobar"; 
           Command (function () -> 
                      print_string "foobar"; 
                      print_newline(); 
                      flush stdout)]
;;
(* b exists but is not yet visible *)
pack [b][] ;;           (* Make b visible *)
mainLoop() ;;           (* User interaction*)
(* You can quit this program by deleting its main window *)

The first step is to initialize the interface with OpenTk, and this returns the toplevel widget. Then, the program creates some widgets, as children of the toplevel widget. These widgets form the user interface of your application. In this simple case, we have only one button, whose behaviour is to print a simple message on the standard output. Finally, the program enters the MainLoop, and user-interaction takes place. Figure shows the interface of this program.

Example

Here is a complete working example with a few widgets and callbacks. This program may be found in the test directory of the distribution, under the name addition.ml.

#open "tk";;

let main () =
  let top = openTk ()  in
  (* The widgets. They all have "top" as parent widget. *)
  let en1 = entry__create top [TextWidth 6; Relief Sunken] in
  let lab1 = label__create top [Text "plus"] in
  let en2 = entry__create top [TextWidth 6 ; Relief Sunken] in
  let lab2 = label__create top [Text "="] in
  let result_display = label__create top [] in
  (* References holding values of entry widgets *)
  let n1 = ref 0
  and n2 = ref 0  in
  (* Refresh result *)
  let refresh () =
    label__configure result_display [Text (string_of_int (!n1 + !n2))]  in
  (* Electric *)
  let get_and_refresh (w,r) =
    fun _ _ ->
      try
       r := int_of_string (entry__get w);
       refresh ()
      with
        Failure "int_of_string" ->
          label__configure result_display [Text "error"]
  in
  (* Set the callbacks *)
  entry__configure en1 [XScrollCommand (get_and_refresh (en1,n1)) ];
  entry__configure en2 [XScrollCommand (get_and_refresh (en2,n2)) ];
  (* Map the widgets *)
  pack [en1;lab1;en2;lab2;result_display] [];
  (* Make the window resizable *)
  wm__minsize_set top 1 1;
  (* Start interaction (event-driven program) *)
  mainLoop ()
;;

printexc__f main () ;;

The XScrollCommand callback has the property of being called each time the insertion cursor blinks in an entry widget. This property is responsible for the ``electric'' behavior of this program. Another approach would have been to associate an action to the event ``key return has been pressed''. Figure shows the interface of this program.

Initialisation

A program using the CamlTk interface must call one of the three following functions :
value openTk : unit -> widget
and openTkClass : string -> widget
and openTkDisplayClass : string -> string -> widget
Each of these functions returns the initial toplevel widget (. in TclTk). openTkClass lets you specify the resource class of the toplevel widget, and openTkDisplayClass lets you specify the display and the resource class.

During the initialisation, the file $HOME/.camltkrc, if it exists, will be loaded by TclTk. This might be the place where to write some special customisations in Tcl, such as changing auto-load path, ...

Widgets

The description of widget commands and options is given in the reference manual (chapter 3). The semantics of commands is however not described there, so the reader should also refer to the man pages of the Tk distribution. As of this release, all widgets of Tk 4.0 are supported, although some commands might not be implemented. Remember also that only few of them have been tested.

Bindings

Tk offers, in addition to widget-specific callbacks, a general facility for binding functions to events. (bind(n)). Bindings involves the following types, defined in the tk module:
type xEvent =
    ButtonPress (* also Button, but we omit it *)
  | ButtonPressDetail of int
  | ButtonRelease
  | ButtonReleaseDetail of int
  | Circulate
  | ColorMap
  | Configure
  | Destroy
  | Enter
  | Expose
  | FocusIn
  | FocusOut
  | Gravity
  | KeyPress (* also Key, but we omit it *)
  | KeyPressDetail of string      (* /usr/include/X11/keysymdef.h *)
  | KeyRelease
  | KeyReleaseDetail of string
  | Leave
  | Map
  | Motion
  | Property
  | Reparent
  | Unmap
  | Visibility 
;;
type modifier =
    Control
  | Shift
  | Lock
  | Button1
  | Button2
  | Button3
  | Button4
  | Button5
  | Double
  | Triple
  | Mod1
  | Mod2
  | Mod3
  | Mod4
  | Mod5
  | Meta
  | Alt 
;;
type eventInfo =
  {
  mutable ev_Above : int;               (* tk: %a *)
  mutable ev_ButtonNumber : int;        (* tk: %b *)
  mutable ev_Count : int;               (* tk: %c *)
  mutable ev_Detail : string;           (* tk: %d *)
  mutable ev_Focus : bool;              (* tk: %f *)
  mutable ev_Height : int;              (* tk: %h *)
  mutable ev_KeyCode : int;             (* tk: %k *)
  mutable ev_Mode : string;             (* tk: %m *)
  mutable ev_OverrideRedirect : bool;   (* tk: %o *)
  mutable ev_Place : string;            (* tk: %p *)
  mutable ev_State : string;            (* tk: %s *)
  mutable ev_Time : int;                (* tk: %t *)
  mutable ev_Width : int;               (* tk: %w *)
  mutable ev_MouseX : int;              (* tk: %x *)
  mutable ev_MouseY : int;              (* tk: %y *)
  mutable ev_Char : string;             (* tk: %A *)
  mutable ev_BorderWidth : int;         (* tk: %B *)
  mutable ev_SendEvent : bool;          (* tk: %E *)
  mutable ev_KeySymString : string;     (* tk: %K *)
  mutable ev_KeySymInt : int;           (* tk: %N *)
  mutable ev_RootWindow : int;          (* tk: %R *)
  mutable ev_SubWindow : int;           (* tk: %S *)
  mutable ev_Type : int;                (* tk: %T *)
  mutable ev_Widget : widget;           (* tk: %W *)
  mutable ev_RootX : int;               (* tk: %X *)
  mutable ev_RootY : int                (* tk: %Y *)
  }
;;
type eventField =
    Ev_Above
  | Ev_ButtonNumber
  | Ev_Count
  | Ev_Detail
  | Ev_Focus
  | Ev_Height
  | Ev_KeyCode
  | Ev_Mode
  | Ev_OverrideRedirect
  | Ev_Place
  | Ev_State
  | Ev_Time 
  | Ev_Width
  | Ev_MouseX
  | Ev_MouseY
  | Ev_Char
  | Ev_BorderWidth
  | Ev_SendEvent
  | Ev_KeySymString
  | Ev_KeySymInt
  | Ev_RootWindow
  | Ev_SubWindow
  | Ev_Type
  | Ev_Widget
  | Ev_RootX
  | Ev_RootY
;;
type bindAction =
   BindSet of eventField list *  (eventInfo -> unit)
 | BindSetBreakable of eventField list *  (eventInfo -> unit)
 | BindRemove
 | BindExtend of eventField list *  (eventInfo -> unit)
;;

Binding are manipulated with the function

bind: widget -> (modifier list * xEvent) list -> bindAction -> unit

The first argument of type widget is naturally the widget for which this binding is defined. The second argument of type (modifier list * xEvent) list is the succession of events that will trigger the callback. The last argument is the action : BindRemove will remove a binding, BindSet will set a binding, and BindExtend will extend a binding. BindSetBreakable is equivalent to BindSet, except that the callback may call the tk__break function to break the chain of callbacks (see break in the bind(n) Tk man page).

In many cases, a callback needs information about the event that triggered it, such as the precise key that was pressed, the position of the mouse, etc. Therefore, a callback is of type EventInfo -> unit, and the callback will be invoked with argument the record containing event information. Since event information can be quite large, the user has to specify, as an EventField list, which fields in this record are relevant for this particular callback. The other fields will not contain accurate information. Check also the Tk documentation for finding out which fields are valid for a given event.

Example
bind myCanvas [[],Motion] (BindSet([Ev_MouseX; Ev_MouseY], mouse_moved)) ;;
will trigger the callback mouse_moved when the event Motion occurs in the myCanvas widget. The function mouse_moved may assume that the information in the fields Ev_MouseX, Ev_MouseY of its argument contain accurate information.

Text and Canvas bindings

Bindings on objets in text and canvas widgets obey the same rules, and the functions are

text__tag_bind : widget -> textTag -> 
      (modifier list * xEvent) list -> bindAction -> unit
canvas__bind : widget -> tagOrId -> 
      (modifier list * xEvent) list -> bindAction -> unit
Additional arguments are respectively the tag and the tag or id for which the binding is defined.

Class bindings

Binding for classes of widgets (resp. tags) are managed with

tk__class_bind : string -> 
      (modifier list * xEvent) list -> bindAction -> unit
tk__tag_bind : string -> 
      (modifier list * xEvent) list -> bindAction -> unit
The first argument should be a widget class name (e.g. "Button"), (resp. a tag name). Widget class names in CamlTk are the exact same names as in Tk.

The tk__bindtags function determines the order of bindings for a given widget.

Text variables

Text variables are available in CamlTk. However, they are not physically shared between Caml Light and Tk, that is, Tk cannot directly modify a Caml Light variable. Instead, setting or consulting a textvariable requires a transaction between Caml Light and Tk. However, the sharing properties of text variables in Tk are preserved. For example, if a text variable is used to physically share the content of an entry with the text of a label, then this sharing will effectively occur in the Tk world, whether your Caml Light program is concerned with the actual contents of the text variable or not.

Since text variables are global in the Tcl space, using a large amount of them might result in space leaks. Thus, there exists a creation function which relates the life of the text variable to the life of a widget. When the associated widget is destroyed, the text variable will be reused by the system.

File descriptor callbacks

A callback can be associated to a file descriptor using the following primitive:
tk__add_fileinput : file_descr -> (unit -> unit) -> unit
When some input is available on the file descriptor specified by the first argument, then the callback (second argument) is called.
tk__remove_fileinput : file_descr -> unit
Removes the file descriptor callback.

It is the programmer's responsability to check for end of file, and to remove the callback if the file descriptor is closed. Be aware that calling the update function will potentially destroy the sequentiality of read operations. Finally, note that during the invocation of your callback, only one call to read is guaranteed to be non-blocking.

tk__add_fileoutput and tk__remove_fileoutput work on the same principle.

Note: there can be only one callback at the same time on a given descriptor. In particular, it is not possible to have an input callback and an output callback simultaneously.

Timers

tk__add_timer : int -> (unit -> unit) -> timer
tk__remove_timer : timer -> unit
A callback can be scheduled to occur after a given delay using tk__add_timer. The first argument is the delay (in milliseconds). A timer callback can be removed before its execution with tk__remove_timer. If the callback has already been executed, tk__remove_timer has no effect. It is not necessary to call tk__remove_timer after a timer callback has been executed.

Exiting

The function
tk__closeTk : unit -> unit
will terminate the main interaction loop (tk__MainLoop) and destroy all widgets.

Errors

The CamlTk interface may raise the following exceptions:

TkError of string
: this exception is raised when a TclTk command fails during an evaluation. Normally, static typing in Caml Light, in addition to some run-time verifications should prevent this situation from happening. However, some errors are due to the external environment (e.g. selection__get) and cannot be prevented. The exception carries the TclTk error message.

IllegalWidgetType of string
: this exception is raised when a widget command is applied to a widget of a different or unappropriate class (e.g. button__configure applied to a scrollbar widget). The exception carries the class of the faulty widget.

Invalid_argument of string
: this exception is raised when some data cannot be exchanged between Caml Light and Tk. This situation occurs for example when an illegal option is used for creating a widget. However, this error may also be caused by a faulty or incomplete description of a widget, widget command or type.

More examples

The test directory of the distribution contains several test examples. The books-examples directory contains translations in CamlTk of several examples from [ouster94].

All examples included in this distribution should not be considered as working applications. They are provided for documentation purposes only.

The Caml Browser

In the directory browser you will find a yet more complete example, featuring many different kinds of widgets. The browser mimicks the Caml Light toplevel behaviour: you can open and close modules (#open and #close directives), or add a library path (#directory directive). You can enter a symbol (value, type constructor, value constructor, exception), and see its definition. You can also see the complete interface of a module by double-clicking on it. Remember that the browser will show you a pretty-printed version of compiled interfaces (.zi files), so you will not see the comments that were in the source file.

The browser can also display source files. Hypertext navigation is also available, but does not reflect the compiler's semantics, as the #open directives in the source file are not taken into account.

When available, the browser also attemps to load TAGS file, created by the mletags program in the Caml Light contributions. It is therefore possible to navigate in program sources. However, note that the browser implements only a simplified version of Emacs's tag mechanism.

Compilation

In the following, we refer to TCLLIBDIR (resp. TKLIBDIR) as an environment variable containing the directory where libtcl.a (resp. libtk.a) is located. Consult your system administrator if you don't have this information.

Moreover, we assume a ``standard installation'' of Caml Light and CamlTk from the distribution. This means that all CamlTk library files are installed in the same directory as standard Caml Light library files.

The usual commands for compiling and linking are:

$ camlc -c addition.ml
$ camlc -custom -o addition tklib.zo addition.zo \
        -ccopt -L$TCLLIBDIR -ccopt -L$TKLIBDIR \
        -lcamltk4 -ltk -ltcl -lX11
Linking is a bit complex, because we need to tell the C compiler/linker where to find the libtk.a and libtcl.a libraries, and to link with all required pieces.

Toplevels

The distribution also installs a toplevel featuring the CamlTk interface: camltktop. This toplevel may be run with:
$ camllight camltktop
Note however that the usage of the toplevel is awkward (for the time being), because callbacks cannot be executed until you enter MainLoop, and you cannot easily leave MainLoop.

Translating Tk idioms

If you are a seasoned Tk programmer, you will find out that some Tk idioms have a different form in CamlTk .

Widgets

First of all, widget creation is more ``functional''. One does not specify the name of the created widget (except for toplevel widgets), but only its parent. The name is allocated by the library, and a handle to the widget is returned to you. Then, widgets are not ``procedures''. They are objects, and must be passed to widget manipulation functions. For example,
button .myf.bok -title "Ok" -relief raised
is translated by (assuming that myf is the parent widget .myf)
let b = button__create myf [Title "Ok"; Relief Raised] in
...
Then,
.myf.bok configure -state disabled
would be in Caml Light:
button__configure b [State Disabled]

This is more in the spirit of ML, but unfortunately eliminates the possibility to specify configuration of widgets by resources based on widget names. It you absolutely want to use resources then you may call the alternate create_named function:

let b = button__create_named myf "bok" [Title "Ok"; Relief Raised] in
...
Assuming that myf is the widget of path .myf, then b will have path .myf.bok. As in Tk, it is your responsibility to use valid identifiers for path elements.

When widgets are mutually recursive (through callbacks, for example when linking a scrollbar to a text widget), one should first create the widgets, and then set up the callbacks by calling configure on the widgets. This is in contrast with TclTk where one may refer to a procedure that has not yet been defined.

Partially applied widget commands (such as redisplay commands) translate quite well to Caml Light, with possibly some wrapping required due to value constructors.

How to find out the Caml Light name of Tk keywords and functions

Normally, a Caml Light command for a widget class will have the same name as the corresponding Tk command. However, in some cases, for example when a same command is used to read and set values, or has a variable number of type incompatible arguments, CamlTk will have several versions of the command with different names.

For the time being the best information sources are:

Debugging

Some Tk functions may have been improperly implemented in the CamlTk library. This may cause undue Tk errors (exception TkError, or sometimes Invalid_Argument). To facilitate the debugging, you can set the Unix environment variable CAMLTKDEBUG to any value before launching your program. This will allow you to see all data transferred between the Caml Light and the Tk processes. Since this data is essentially TclTk code, you need a basic knowledge of this language to understand what is going on. It is also possible to trigger debugging by setting the boolean reference protocol__debug to true (or false to remove debugging).

Changes from previous versions of CamlTk for Tk4.0

There has been really a lot of changes.

Changes from previous versions of CamlTk for Tk3.6

This list attempts to describe the incompatible changes, but is probably not exhaustive.


Next Contents