Uni Bremen

 

ALU Freiburg

FB3- Mathematics and Computer Science

Institut für Informatik


sml_tk: Functional Programming for Graphical User Interfaces

Release 3.0

Christoph Lüth and Burkhart Wolff


Contents:

1. A Gentle Introduction to sml_tk

2. Design Issues

3. The sml_tk Reference Manual

4. The sml_tk Toolkit

5. Installation

6. Bibliography

7. Appendix: A Commented Logfile


1. A Gentle Introduction to sml_tk

What is sml_tk?

sml_tk is a Standard ML (SML) package that provides a portable, typed and abstract interface to the user interface description and command language Tcl/Tk. sml_tk was originally developed in the UniForM-Project and has been continually developed since by the universities of Bremen and Freiburg.

Tcl/Tk consists of two components, which in principle are independent: the interface toolkit Tk offering a highly portable interface to various operating systems and their graphical display engine (such as Windows, MacOS and UNIX with its X Window System) and the command language Tcl, a weakly typed Lisp-like scripting language. (For more information, see [Oust94], [REF 96], [HM 97], [Fos 97], [Wel 97] or the home page). While we appreciate both Tk's portability and level of abstraction to access a window system, we do not subscribe to the Tcl design philosophy that one should have a weakly typed and structured scripting language to tie together smallish applications written in another language. We believe that this leads to all the kind of drawbacks one wants to avoid by using a structured language in the first place (code that is ugly to read, prone to errors and hard to maintain), and that one should use a structured language which offers structuring concepts in-the-large for this purpose, such as Standard ML [Pau94] with its module language. Hence, we prefer to use Tk to build graphical user interfaces for applications written in the functional language SML via an interface from SML directly.

The package sml_tk has been designed to combine the advantages of the Tk toolkit with those of SML. As a consequence, a novice in interface programming can skip the first fifteen chapters of the reference book [Oust94] dealing with Tcl and concentrate on those parts of the book addressing the visual appearance of graphical objects and their layout control using Tk. sml_tk covers most of the Tk functionality, and is easy to extend.

Part of the sml_tk package is a toolkit library of reusable interface building components, including

This library highly profits from the SML module system. For example, GenGUI is a functor which when instantiated with an application yields a graphical user interface controlling that application. We have used this toolkit library to implement user interfaces for applications based on theorem provers (see [LW99] and [LW00]).

The core of sml_tk implements the following entities:

Each of these corresponds to an SML datatype, called Window, Widget, Annotation and CItem respectively. Any of these objects must have a unique identification ("id") that is used to reference them. The management of these identifications and their mapping to appropriate names in Tcl is handled by sml_tk. The central datatype Widget provides one variant for each graphical entity like buttons, labels, menus, text fields, etc. They are usually composed of the following:

Windows are essentially given by

Such a window, described by an ML expression, can either be passed to the function startTcl that creates a GUI (consisting of this window or possibly a list of windows) as a reactive system, or to suitable operations that create windows dynamically during the lifetime of the reactive system. While the reactive system is running, the input from the SML interpreter is blocked, so the function startTcl only returns once the reactive system has terminated (i.e. the main window has been closed again).

A Small Example

To give a flavour of programming in sml_tk, we sketch a small example: suppose we want to construct a small window consisting of a text entry field labelled "name:" left from this field, and a quit button. After clicking on the text field, new text can be entered and line-edited. After finishing by hitting the Return key, the entered text should rename the window. When the quit button is pressed (i.e. clicked on with the mouse) the window should be closed, and the example terminates.

After opening the structures TkTypes and SmlTk, which contain the export signature of sml_tk as described in detail below, we are ready to start. We will have three graphical entities (widgets):

All of these are of the type widget, using the three constructors Entry, Label and Button. We first need two identifiers, one for the main window and one for the text entry widget, which we generate by the two functions newWinId and newWidgetId, respectively:

val mainID = newWinId()
val entID = newWidgetId()

The other two widgets can remain anonymous, since we need not refer to them explicitly.

(* Define the widgets: *)

val lab = Label{widId=newWidgetId(), packings=[Side Left]
                configs=[Text "name:"], bindings=[]}
val input = let fun endInput _ = changeTitle mainID (mkTitle(readTextAll entID))
            in Entry{widId=entID, packings=[], configs=[Width 20],
                     bindings=[BindEv(KeyPress "Return",endInput)]}
            end
val quit = let fun stop _ = closeWindow mainID
           in Button{widId=newWidgetId(), packings=[Side Bottom],
                     configs=[Text "Quit", Command stop],
                     bindings=[]}
           end

The action endInput is bound to the the Return key, and the button labelled Quit is configured such that the action stop, which closes the main window and hence terminates the application, is executed when the button is pressed.

The layout is achieved as follows: the two widgets label and input should be placed side by side on top, with the widget quit at the bottom. To put two widgets side by side, we put them in one widget called a frame:

val topblock = Frame{widId=newWidgetId(), widgets=Pack [lab, input],
                          packings=[Side Top], configs=[], bindings=[]}

We are now ready to create the main window:

val enterwin = {winId = mainID,
                config = [WinTitle "Please enter name"],
                widgets = Pack [topblock, quit],
                bindings = [],
                init = noAction}

Note that the order of the widgets is not important in this particular example. However, in general order here is relevant. Moreover, the result of the Tk layout and packing algorithm can be somewhat surprising, and may require some experimentation.

We now have to initialize sml_tk; this has to be done before any sml_tk application is started. After that, we can start our small GUI:

SmlTk.init();
startTcl [mkWindow enterwin];

The following window appears on the screen:

[Imagine asmall window here.]

We can now click on the entry field, and type in some text, finishing with <Return>, and we get:

[Imagine asmall window here with its title changed to qwerty]

"Pressing" the quit button (i.e. moving the cursor over it and pressing a mouse button) closes the window and terminates the startTcl command.

About this document

The purpose of this documentation is:

A detailed description of small examples and some tricks and debugging techniques may help beginning programmers. We suggest using some of the smaller examples and extending them stepwise - many frustrating beginner's errors are avoided this way. The example above can be found in the directory src/tests+examples in the file small_ex.sml; that directory also contains many more examples that the beginning sml_tk programmer is encouraged to have a look at.

This document is not intended to replace a Tk-Reference Manual, rather to be complementary. For serious working with sml_tk the user will constantly want to consult this document in order to get more information on legal options or the visual appearance of widgets.

History and Acknowledgements

A first version of sml_tk [KSW96] was developed from GoferTK [VTS95]. After translating it from a lazy pure functional language (Gofer) into an eager impure functional language (SML), and the extension of the functionality in the following versions, only a passing similarity remains, but we acknowledge the ancestry. We also snatched some code for the TCL_INSIDE configuration on the C-level.

More recently, Elsa Gunter and Dave McQueen of Bell Labs have made many suggestions and contributions which we gratefully acknowledge; in particular, Elsa Gunter has provided the implementation for polygons, arrowheads, and mixed colours.

For the current release, Erik Behrends provided code for the TCL_INSIDE configuration, and Andre Lüdtke many invaluable improvements on both the kernel and the toolkit (in particular, the filer).

2. Design Issues

Technically, sml_tk starts a Tcl interpreter (called a wish for windowing shell) either as subprocess or a subroutine. During runtime, sml_tk compiles the description of the user interface (given by Widgets, Windows, etc described above) into Tcl expressions which are passed to the wish.
sml_tk comes in two configurations:

sml_tk keeps an internal GUI state consisting of a list of windows and additional information. This state is a logical image of the (external) state of the wish. This is pretty much in the style of [VTS95], but in contrast to the Tk interface caml_tk [PR95], where widgets and windows are built by successive calls of functions with side effects; in sml_tk, the data types for widgets and windows are freely generated in order to support a more functional style of programming, with less side effects.

The functionality of sml_tk can be divided into the following categories:

  1. constructors and selectors (selXX) for all datatypes;
  2. functions for dynamically adding, deleting and updating (exchanging) widgets, widget components or windows to the GUI;
  3. functions to inspect components in the GUI state (getXX)
  4. functions to inspect the actual state of the wish (readXX);
  5. functions to set the actual state of the wish (setXX, insertXX)

The user can change the state of the wish by inserting text into a text widget, by clicking on a radio button, by selecting a text in a listbox, by inserting a cursor into a text, by manipulating a scrollbar or by resizing a window. Since it would require far too much implementation overhead to enforce the equivalence between GUI state and state of the wish automatically and at all times, the distinction between the getXX-functions and the readXX-functions is crucial. In many cases, configuration and coordinates in the GUI state represent only the initial value, while the actual values have to be explicitly read from the wish. This is especially the case for Tcl variables, cursor positions within a texts, and texts editable by the user.

The GUI state is particularly useful to realize a binding mechanism. To most widgets, bindings can be annotated with the constructor BindEv, which takes as arguments an event and an action. An event is given by the structured data type Event, which models events as given in Tcl (see [Oust94], Chapter 8, pp. 199) in a structured way (examples are the events ButtonPress(SOME 1) for the first mouse button being pressed, or KeyPress "Return" for the Return key being pressed), and an action, which is an ML function of the functionality TkEvent-> unit. TkEvent is a data type containing information on the cursor position (both relative to the window and absolute and the buttons pressed) etc. sml_tk will map the ML functions (actions) to the events as extracted from the GUI state, and call the relevant ML function when the specified event occurs.

Tk is not completely orthogonal. In some cases, configuration options for a widget are expressed differently for each individual widget; sml_tk allows to abstract from these. However, the problem remains that not every component can be arbitrarily combined with each other, e.g. only particular widgets are allowed to build a menu. In Tk these constraints are checked dynamically. Since SML does not contain a subclass concept like Haskell which would allow to express such contextual side conditions on the type level, a compromise had to be made between precision in modelling the data types on the one hand and usability, flexibility and simplicity of the sml_tk user interface on the other. This compromise consists in collecting all configuration options into one data type, leaving violations to dynamic checks, while in all other places the construction of widgets is as accurate as possible.

Since Tcl does not provide any data type other than texts, and in particular no constructors for aggregations like arrays or lists, some ad-hoc constructs had been introduced to express collections and groups of objects. Since SML offers better ways to represent aggregations, sml_tk does not support these concepts and hence sometimes simplifies the view of Tk. This holds also for scrollbars (on text widgets and canvas widgets) which are no longer widgets in their own right in sml_tk.

An important design decision was made in binding SML functions (rather than threads) to events. This is the basis for sml_tk's simplicity; available concurrency toolkits (such as John Reppy's Concurrent ML, which unfortunately is based on cooperative multitasking) would limit sml_tk to particular SML implementations without providing a full solution to important requirements (such as a kill-button for diverging threads). For these reasons we stuck to the binding of functions to events (giving the control to the application during its evaluation) and provided an ad-hoc solution for the kill-button (see below).

3. The sml_tk Reference Manual

This chapter contains a technical description of sml_tk's export interface in a reference manual style. The export interface consists of two signatures:

The two signatures correspond to the way in which an sml_tk application's interface is build: firstly, using the data types from TK_TYPES, the initial appearance of the interface is specified in a declarative way. Then control is passed to sml_tk, and the runtime behaviour of the application is a reactive system, i.e. the application's behaviour is a reaction to the user's input, implemented using the functions from SML_TK which allow dynamic changes to the interface and much more.

The main structure of sml_tk is the structure SmlTk. Its export interface consists of a join of the two signatures TK_TYPES and SML_TK, which completely restrict the implementing structure (also in the file sml_tk/src/export.sml). In SML terminology, the joint signature is the opaque  interface to sml_tk.

3.1. The Signature TK_TYPES

This section describes the signature TK_TYPES. It mainly contains the data types, constructors and selectors for freely generated term structure by which the user interface can be specified in a declarative way.

The data structures closely mirror the Tk concepts. In order to understand these concepts, such as the different kinds of widgets, their purpose and configuration options, the graphical layout and the behaviour of the Tk layout algorithm (the "packer"), it will be necessary to consult the Tcl/Tk-book [REF 96] or the manual pages from the Tcl/Tk distribution package. This documentation will not try to explain these Tk concepts, but rather point out where sml_tk behaves differently from Tk.

3.1.1 Identifiers

The following types are identifiers for windows, widgets, canvas items, text annotations and images, respectively. See section 3.2.1 below how to create identifiers.

 type WinId
 type WidId
 type CItemId
 type AnnId
 type ImageId

3.1.2. Exceptions

Exceptions usually indicate a programming error. Favourite examples include referring to non-existing widgets, windows or canvas items, wrong configurations (some widgets require certain configurations whereas others prohibit them), applying partial selectors to the wrong variant of the data type, using non-existing fonts or loading non-existing images.

 exception CITEM of string
 exception WIDGET of string
 exception TCL_ERROR of string
 exception CONFIG of string
 exception WINDOWS of string

There are two functions by which to start an sml_tk application; one catches these exceptions and prints the relevant string (containing an error message), and one does not (see section 3.2.2 below). It should be pointed out these exceptions indicate some kind of malfunction, usually a programming error, so just catching them and continuing is not such a bright idea.

3.1.3. TkEvents, Actions and Bindings

When running as reactive system, activation is driven by events such as mouse buttons or keys being pressed, or widgets (to be exact, their graphical representation on the screen) being entered or left with the cursor.

The most common reaction in Tk applications is that of a Command. Commands are specified as part of the Configure information in widgets such as buttons.

 type SimpleAction = (unit -> unit)

For finer control of the interactive behaviour of the application, the data type Binding specifies a particular event and the corresponding reaction.

 datatype Binding = BindEv of Event * Action

Events are given by the datatype Event. Events are either basic events, or modifications of events. Basic events are:

These can be modified as follows:

A final different kind of event is given by UserEv, which takes as argument a description of events in the style of Tcl (see [Oust94] section 18.3). This kind of event description is strongly discouraged, though, since syntactically wrong event descriptions lead to a wish runtime error.

 datatype Event =
 (* window events *)
 FocusIn
 | FocusOut
 | Configure
 | Map
 | Unmap
 | Visibility
 | Destroy
 (* Key press/release events *)
 | KeyPress of string
 | KeyRelease of string
 (* Button press/release events, NONE means any old Button *)
 | ButtonPress of int Option.option
 | ButtonRelease of int Option.option
 (* Cursor events *)
 | Enter | Leave | Motion
 (* user-defined events, or explicitly given events *)
 | UserEv of string
 (* event modifiers *)
 | Shift of Event | Ctrl of Event | Lock of Event | Any of Event
 | Double of Event | Triple of Event
 | ModButton of int* Event
 | Alt of Event | Meta of Event
 | Mod3 of Event | Mod4 of Event | Mod5 of Event

Not all possible combinations make sense though; for example, a button event modified with a different button, or a doubled enter event are clearly paradoxical (and hence will never occur, so binding anything to them is a lost cause). An Action is an ML function to be called when the specified event occurs:

 type Action = (TkEvent -> unit)

Events have the following structure:

 
datatype TkEvent =
 TkEvent of int (* %b button number *)
          * string (* %s state field *)
          * int (* %x x field *)
          * int (* %y y field *)
          * int (* %X x_root field *)
          * int (* %Y y_root field *)

The corresponding selector functions are:

 
 val selButton   : TkEvent -> int
 val selState    : TkEvent -> string
 val selXPos     : TkEvent -> int
 val selXRootPos : TkEvent -> int
 val selYPos     : TkEvent -> int
 val selYRootPos : TkEvent -> int

When no binding for an event has been specified, nothing happens, e.g. a button press in a canvas widget is simply ignored if no binding has been specified and does not lead to an error.

With some widgets or items, not all events are allowed in their binding; further, the information in TkEvent is only valid for some, but not all of the events. See chapter 18 in the Tcl/Tk handbook [Oust94] or the bind manual page about the details of events and bindings.

Be also warned that the interaction between mouse button presses and mouse movements is fairly intricate: when a mouse button is being pressed, all subsequent mouse events go to the widget or item over which this event has occurred until the mouse button is released. In particular, no enter event is generated if the mouse is moved into another widget, item or window with the mouse button pressed, the enter event will only be generated once the mouse button has been released.

Finally, be aware that the keyboard focus (see section 3.2.14) determines where keyboard events go. It will rarely make sense to bind actions to keys in widgets other than text widgets or entry widgets, since these other widgets will usually not be in a position to receive keyboard events.

3.1.4. Fonts

Fonts in sml_tk are described by the data type Font, which offers a slight abstraction over the standard X11 description. The idea is to provide a set of standard fonts, which can be modified for style and size. These standard fonts are mapped (on calling SmlTk.init) to specific X11 fonts. If a specific font is not found (e.g. in a particular size or weight), another one is selected. This approach has been chosen because fonts are provided by the X server, and hence can change; this way, applications can still use different fonts, but are not dependent on any particular X11 font being present in the server.

The standard fonts are Normalfont, Typewriter, SansSerif and Symbol. They can be modified as Bold, Italic, Tiny (very small), Small, NormalSize, Large and Huge. The modifier Scale allows to scale fonts arbitrarily, where Scale 1.0 has the same effect as NormalSize. Users insisting on a particular X11 font can obtain these by XFont.

 datatype FontConfig = Bold | Italic |
                       Tiny | Small | NormalSize | Large | Huge |
                       Scale of real

 datatype Font = XFont of string
               | Normalfont of FontConfig list
               | Typewriter of FontConfig list
               | SansSerif of FontConfig list
               | Symbol of FontConfig list

The mapping of the standard fonts to the X11 font implementing them is described by the type Fonts.Config. The provided defaults are as follows:

 
val Config = {Normalfont = ref "-*-courier",
              Typewriter = ref "-misc-fixed",
              SansSerif = ref "-*-helvetica",
              Symbol = ref "-*-symbol",
              BaseSize = ref 12,
              ExactMatch = ref true,
              Resolution = ref 75
             }

3.1.5. Widget Configuration Options

Configure information correspond to Tk's options. It is the information used to customise the predefined widgets. Configure options which have not been specified are left open, and the default for the widget type in question is chosen.

The effects and intended use of the various configuration options, and which option is valid for which kind of widget, can be found in the Tk manual. The following configure options are supported by sml_tk. For every constructor we give the corresponding Tk option for easier reference to the Tk manual pages.

 datatype Configure =
 Width of int (* -width ... *)
 | Height of int (* -height ... *)
 | Borderwidth of int (* -borderwidth ... *)
 | Relief of RelKind (* -relief ... *)
 | Foreground of Color (* -foreground ... *)
 | Background of Color (* -background ... *)
 | MUnderline of int (* -underline ... for menus *)
 | Accelerator of string (* -accelerator "bla" *)
 | Text of string (* -Label "bla" *)
 | Font of Fonts.Font (* -font "bla" *)
 | Variable of string (* -variable "bla" *)
 | Value of string (* -value "bla" *)
 | Icon of IconKind (* -bitmap or -image ... *)
 | Cursor of CursorKind (* -cursor ... *)
 | Command of SimpleAction (* -command ... *)
 | Anchor of AnchorKind (* -anchor ... *)
 | FillColor of Color (* -fill ... *)
 | Outline of Color (* -outline ... *)
 | OutlineWidth of int (* -width ... *)
 | Smooth of bool (* -smooth .. *)
 | Arrow of ArrowPos (* -arrow ... *)
 | ScrollRegion of int * int * int * int (* -scrollregion ... *)
 | Capstyle of CapstyleKind (* -capstyle ... *)
 | Joinstyle of JoinstyleKind (* -joinstyle ... *)
 | ColorMap of ColorMapEntry list (* -colormap ... *)
 | ColorMode of colorMode (* -colormode ... *)
 | File of string (* -file ... *)
 | FontMap of FontMapEntry list (* -fontmap ... *)
 | PrintHeight of string (* -height ... *)
 | PageAnchor of AnchorKind (* -pageanchor ... *)
 | PageHeight of string (* -pageheight ... *)
 | PageWidth of string (* -pagewidth ... *)
 | PageX of string (* -pagex ... *)
 | PageY of string (* -pagey ... *)
 | Rotate of bool (* -rotate ... *)
 | PrintWidth of string (* -width ... *)
 | PrintX of string (* -x ... *)
 | PrintY of string (* -y ... *)
 | Offset of int (* -offset ... Offset over baseline for texts *)
 | Underline (* -underline ... underline for texts (see MUnderline above) *)
 | Justify of Justify (* -justify ... Justification: left/right/center *)
 | Wrap of WrapMode (* -wrap ... *)
 | Orient of Orientation (* -orient ... *)
 | Active of bool (* -state ... *)

 (* Scale configurations *)
 | SLabel of string (* -label ... *)
 | Length of int (* -length ... *)
 | SliderLength of int (* -sliderlength ... *)
 | From of real (* -from ... *)
 | To of real (* -to ... *)
 | Resolution of real (* -resolution ... *)
 | Digits of int (* -digits ... *)
 | BigIncrement of real (* -bigincrement ... *)
 | TickInterval of real (* -tickinterval ... *)
 | ShowValue of bool (* -showvalue ... *)
 | SliderRelief of RelKind (* -sliderrelief ... *)
 | SCommand of ScaleAction (* -command ... special command for use with scales (called with the scale value) *)
 | RepeatDelay of int (* -repeatdelay ... *)
 | RepeatInterval of int (* -repeatinterval ... *)
 | ThroughColor of Color (* -throughcolor ... *)

 | InnerPadX of int (* -padx ... *)
 | InnerPadY of int (* -pady ... *)
 | Show of char (* -show ... (password entry fields etc.) *)
 | Tearoff of bool (* -tearoff ... tearoff for menus *)

Relief

 datatype RelKind = Flat | Groove | Raised | Ridge | Sunken

The relief customises the visual three-dimensional appearance (see [Oust94] section 16.1.1) of widgets.

Colours

 
datatype Color = NoColor | Black | White | Grey | Blue | Green | Red | Brown | Yellow
               | Purple | Orange | Mix of {red : int, blue : int, green : int}

We apologise to British users for the spelling (and blame Tk for it).

Anchor Kind

 datatype AnchorKind = North | NorthEast |
                       East | SouthEast |
                       South | SouthWest |
                       West | NorthWest |
                       Center

The option AnchorKind is used for the orientation of an object with respect to their position (anchor) (see [Oust94] section 16.11.2).

Capstyle and Joinstyle

These datatypes describe how lines, when drawing on a canvas, are joint.

 datatype CapstyleKind = Butt | Projecting | Round
 datatype JoinstyleKind = Bevel | Miter | RoundJoin

Icons and Images

 type BitmapName
 type BitmapFile
 type ImageFile

 datatype IconKind =
 NoIcon
 | TkBitmap of BitmapName (* -bitmap <tk bitmap> *)
 | FileBitmap of BitmapFile (* -bitmap @<filename> *)
 | FileImage of ImageFile * ImageId (* -image ... *)

TkBitmap and FileBitmap allow the usage of X11 bitmaps. BitmapName must be the name of one of Tk's internal bitmaps. BitmapFile specifies a pathname of a file with the appropriate format. Bitmaps can be used with some widgets such as Label or Button (see also the bitmap manual page or [Oust94] section 16.3.3), and in canvas items (see section 3.1.7).

Tk also supports images (with depth more than 1). Images are specified with FileImage, where ImageFile is the pathname of a file in a recognised format such as GIF (for other formats supported see the image manual page). Examples can be found in src/tests+examples/big_ex.sml.

The three types BitmapName, BitmapFile and ImageFile are synonymous with string.

Cursors

 type CursorName
 type CursorFile

 datatype CursorKind =
 NoCursor
 | XCursor of CursorName * ((Color * (Color option )) option )
 | FileCursor of CursorFile * Color * ((CursorFile * Color) option )

With XCursor the predefined Tk cursors can be used, where the argument CursorName is the name of a Tk cursor. The next two optional arguments are for the foreground and background colour of the bitmap cursor.

One can also use self-designed cursors via the constructor FileCursor. The first argument is the name of the bitmap file (CursorFile) for the cursor, the second the foreground colour and the last two optional arguments are a mask file (CursorFile) and the respective background colour.

The types CursorName and CursorFile are synonymous with string.

Examples can be found in src/tests+examples/big_ex.sml and details in [Oust94] section 16.11.1.

If users find they need to use Tk options which are not supported by sml_tk, they are invited to add support for them; look at src/config.sml to see how the other configurations are translated in Tcl code (and that is essentially all there is to do).

3.1.6. Controlling the Layout

The layout in sml_tk is done with the Tk packer. Since release 3.0 sml_tk also supports grid packing, which means that you also place widgets in a specified row and column of a grid within a container. You can choose the packing algorithm using the Pack or Grid constructor of the type Widgets, which is used to specify the initial content of a container (containers in sml_tk are Frame as a normal container widget, TAWidget for widgets within annotated texts, CWidget for widgets within canvases, and of course windows). Once chosen you can not change the applied packing algorithm. The programmer is strongly encouraged to read chapter 17 of [Oust94] and try out some of the examples, as the Tk packer exhibits a somewhat intractable behaviour with occasionally rather surprising results; hence a certain amount of experimentation and experience will not go amiss.

Bear in mind that

sml_tk implements a basic subset of Tk's pack options and possibilities. These are:

 datatype Edge = Top | Bottom | Left | Right
 datatype Style = X | Y | Both
 datatype StickyKind = N | S | E | W | NS | NE | NW | SE | SW | EW | NSE | NSW | NEW | SEW | NSEW

 datatype Pack = Expand of bool
               | Fill of Style
               | PadX of int
               | PadY of int
               | Side of Edge
               | Column of int
               | Row of int
               | Sticky of StickyKind

3.1.7. The core of sml_tk: Widgets and their Components

Widgets (and their components) are the most important and powerful datatype of sml_tk (and Tk). They are the basic building blocks of any gui.

The different types of widgets are explained in the chapter A guided Tour through Tk Widgets in [Oust94]. sml_tk offers one constructor for every widget type. Selector and update functions on widgets are given where useful.

Some widgets like frames (a grouping of widgets), canvas widgets (a "drawing pad") and text widgets can contain either widgets themselves, or items (canvas items or text annotations) which contain widgets; this results in a mutually recursive, fairly lengthy data type definition.

As remarked above, the Widget, CItem and Annotation data types have some parameters which are only intended as initial values. They appear in italics below. If one wants to get the actual value of such an initial value, one has to use a readXXX function rather than a getXXX or selXXX function.

datatype Widget =
 Frame of {widId : WidId, widgets : Widgets,
           packings : Pack list, configs : Configure list,
           bindings : Binding list}
 | Message of {widId : WidId, packings : Pack list,
               configs : Configure list, bindings : Binding list}
 | Label of {widId : WidId, packings : Pack list,
             configs : Configure list, bindings : Binding list}
 | Listbox of {widId : WidId, scrolltype : ScrollType,
               packings : Pack list, configs : Configure list,
               bindings : Binding list}
 | Button of {widId : WidId, packings : Pack list,
              configs : Configure list, bindings : Binding list}
 | Radiobutton of {widId : WidId, packings : Pack list,
                   configs : Configure list, bindings : Binding list}
 | Checkbutton of {widId : WidId, packings : Pack list,
                   configs : Configure list, bindings : Binding list}
 | Menubutton of {widId : WidId, mitems : MItem list,
                  packings : Pack list, configs : Configure list,
                  bindings : Binding list}
 | Entry of {widId : WidId, packings : Pack list,
             configs : Configure list, bindings : Binding list}
 | TextWid of {widId : WidId, scrolltype : ScrollType,
               annotext : AnnoText, packings : Pack list,
               configs : Configure list, bindings : Binding list}
 | Canvas of {widId : WidId, scrolltype : ScrollType,
              citems : CItem list, packings : Pack list,
              configs : Configure list, bindings : Binding list}
 | Popup of {widId : WidId, tearoff : bool, mitems : MItem list}
             mitems : MItem list}
 | ScaleWid of {widId : WidId, packings : Pack list,
                configs : Configure list, bindings : Binding list}
 and Widgets = Pack of (Widget list)
             | Grid of (Widget list)

The type Widgets represents a collection of widgets, packed either with Tk's Packer geometry manager, or with the Grid geometry manager. Every widget has an identifier of the type WidId, and pack information, configuration options and bindings. Other parameters are widget specific, such as The following are useful selector and update functions for Widget. (Note that you can't update the widget identifier.) Some of these are necessarily partial, and when applied to the wrong variant of widget will raise the exception WIDGET. First, the ones applicable to all widgets:

 
 val selWidgetId   : Widget -> WidId
 val selWidgetBind : Widget -> Binding list
 val selWidgetConf : Widget -> Configure list
 val selWidgetPack : Widget -> Pack list

 val updWidgetBind : Widget -> Binding list -> Widget
 val updWidgetConf : Widget -> Configure list -> Widget

Functions specific to Canvas: val updWidgetPack : Widget -> Pack list-> Widget

 
 val selCanvasItems      : Widget -> CItem list
 val selCanvasScrollType : Widget -> ScrollType

 val updCanvasItems      : Widget -> CItem list -> Widget
 val updCanvasScrollType : Widget -> ScrollType -> Widget

Functions specific to TextWid:

 
 val selTextWidScrollType  : Widget -> ScrollType
 val selTextWidText        : Widget -> string
 val selTextWidAnnotations : Widget -> Annotation list

 val updTextWidScrollType  : Widget -> ScrollType -> Widget
 val updTextWidAnnotations : Widget -> Annotation list -> Widget

Scrollbars

As opposed to Tk, sml_tk does not offer a dedicated widget type for scrollbars. Rather, for the widget types for which scroll bars are useful- text widgets, canvas widgets, and list boxes- scrollbars are provided automatically. Their location is determined by the argument ScrollType in their constructor (where NoneScb means no scroll bar at all).

 datatype ScrollType = NoneScb | LeftScb | RightScb | TopScb | BotScb |
 LeftTopScb | RightTopScb | LeftBotScb | RightBotScb

This also has the advantage that the programmer need not to concern himself with the programming of the scroll bar.

Canvas Items

A canvas widget is a "drawing pad". Drawing on a canvas is done by placing the following canvas items onto it, and as opposed to normal widgets, the location of the items is exactly specified by means of coordinates, rather than left to a packing algorithm. In the toolkit library, canvas widgets have been used to implement a drag&drop package and a generic user interface (see section 4).

sml_tk provides all types of canvas items found in Tk. These are:

 datatype CItem =
 CRectangle of {citemId : CItemId, coord1 : Coord, coord2 : Coord,
                configs : Configure list, bindings : Binding list}
 | COval of {citemId : CItemId, coord1 : Coord, coord2 : Coord,
             configs : Configure list, bindings : Binding list}
 | CLine of {citemId : CItemId, coords : Coord list,
             configs : Configure list, bindings : Binding list}
 | Cpoly of {citemId : CItemId, coords : Coord list,
             configs : Configure list, bindings : Binding list}
 | CText of {citemId : CItemId, coord : Coord,
             configs : Configure list, bindings : Binding list}
 | CIcon of {citemId : CItemId, coord : Coord, iconkind : IconKind,
             configs : Configure list, bindings : Binding list}
 | CWidget of {citemId : CItemId, coord : Coord, widgets : Widgets,
               configs : Configure list, bindings : Binding list}
 | CTag of {citemId : CItemId, citemIds : CItemId list}

As already noted above, italic parameters are the initial value. Coordinates as arguments of the constructors allow convenient specification of the initial position.

The following are the selector and update functions for CItem. Again, some of them will be necessarily partial and when applied to the wrong type of widget will raise an exception CITEM. First, the functions applicable to most or all canvas items:

 val selItemId : CItem -> CItemId
 val selItemCoords : CItem -> Coord list
 val selItemConf   : CItem -> Configure list
 val selItemBind   : CItem -> Binding list

 val updItemCoords : CItem -> Coord list -> CItem
 val updItemConf   : CItem -> Configure list -> CItem
 val updItemBind   : CItem -> Binding list -> CItem

The following are specific to CIcon:

 val selItemIcon : CItem -> IconKind

 val updItemIcon : CItem -> IconKind -> CItem

Within a CIcon item one can have the same bitmaps and images as presented in section 3.1.5.

The following are specific to CWidget:

 val selItemWidId : CItem -> WidId
 val selItemWidgetConf : CItem -> Configure list
 val selItemWidgets : CItem -> Widget list

 val updItemWidgetConf : CItem -> Configure list -> CItem
 val updItemWidgets : CItem -> Widget list -> CItem

CWidget allows widgets within canvas items. Tk offers to have one widget within a window item. We found it useful to have more than one, so an sml_tk widget canvas item contains a list of widgets, which are placed within a frame widget. The WidId and Configuration arguments of the CWidget constructor refer to this frame widget. The identifier for the frame widget must be obtained by using the function newCItemFrameId (see section 3.2.1).

The following functions are specific for CTag:

 val selItemItems : CItem -> CItemId list

 val updItemItems : CItem -> CItemId list -> CItem

CTag is only a very weak way of structuring canvas items. Many of the operations on CItems are not applicable to CTag, but e.g. moveCItem and setCItemCoords are, giving a convenient way of moving about groups of canvas items.

Text Annotations and Annotated Texts

Text annotations are bindings and configurations for specific parts of text within a text widget. They can be used for two purposes, corresponding to the two variants of the data type Annotation below:

Positions or regions within a text are specified with the data type Mark. For historical reasons, sml_tk's marks are Tk's indices. For details, see [Oust94] section 19.6.

 datatype Mark = Mark of int (* line number [1..] *)
                        * int (* char number [0..] *)
    | MarkToEnd of int (* end of line i *)
    | MarkEnd (* end of text *)


 and Annotation =
 TATag of {annId : AnnId, marks : (Mark * Mark) list,
           configs : Configure list, bindings : Binding list}
 | TAWidget of {annId : AnnId, mark : Mark, widId : WidId,
           widgets : Widgets, configs1 : Configure list,
           configs2 : Configure list, bindings : Binding list}

As already noted italic arguments are only initial values. The position arguments of the constructors allow convenient specification of their initial position.

The following are the selector and update functions for Annotation. First, those that are applicable to both variants of annotations:

 val selAnnotationId : Annotation -> AnnId
 val selAnnotationConf : Annotation -> Configure list
 val selAnnotationBind : Annotation -> Binding list
 val selAnnotationMarks : Annotation -> (Mark * Mark) list

 val updAnnotationConf : Annotation -> Configure list -> Annotation
 val updAnnotationBind : Annotation -> Binding list -> Annotation

selAnnotationMarks applied to a TAWidget returns a singleton list containing a pair of two marks both equal to the position of the widget annotation, i.e.

 selAnnotationMarks(TAWidget(_,p,_,_,_,_,_)) = [(p, p)]

The following functions are specific to TAWidget and will raise an exception ANNOTATION when applied to TATag.

 val selAnnotationWidId : Annotation -> WidId
 val selAnnotationWidgets : Annotation -> Widget list
 val selAnnotationWidgetConf : Annotation -> Configure list

 val updAnnotationWidgets : Annotation -> Widget list -> Annotation

TAWidget allows to embed widgets within a text. Tk offers to have one widget within a window annotation. We found it useful to have more than one, so an sml_tk widget annotation contains a list of widgets, which are placed within a frame widget. The WidId and Configuration arguments of the TAWidget constructor refer to this frame widget. The identifier for the frame widget must be obtained by using the function newAnnotationFrameId (see section 3.2.1).

Annotated Texts

Annotated texts consist of a text, given by a string, and a list of annotations within that text. They are provided as a separate datatype because this way they can be treated abstractly; e.g. there is a function which concatenates two annotated texts, adjusting the annotations in the second text (see section 3.2.9). The first, optional argument of the constructor AnnoText in the following is the length of the text (in lines and columns); if it is not given, it will be calculated on a by-need basis.

 and AnnoText =
 AnnoText of {len : (int* int) Option.option, str : string,
 annotations : Annotation list}

The selectors and update functions for annotated texts are

 val selText : AnnoText -> string
 val selAnno : AnnoText -> Annotation list
 val updAnno : AnnoText -> Annotation list -> AnnoText

Annotated texts can be conveniently produced with sml_tk's markup language and the generic parser provided for it (see section 4.2), which alleviates the need to meticulously count lines and columns within a text.

Menu Items

A menu is a list of the following items:

 and MItem = MCheckbutton of (Configure) list
            | MRadiobutton of (Configure) list
            | MCascade of MItem list * Configure list
            | MSeparator
            | MCommand of (Configure) list

These correspond to the Tk type. Note that for MCheckbuttons and MRadiobutton, a Tcl variable has to be specified (using the Variable configuration option) holding the status of the check or radio button. Their current status can be read using readVarValue (see section 3.2.10 below). Following are the selectors for MItem:

 val selMCommand : MItem -> SimpleAction
 val selMRelief : MItem -> RelKind
 val selMText : MItem -> string
 val selMWidth : MItem -> int
 val selMItemConfigure : MItem -> Configure list

3.1.8. Windows

A window is a quadruple of a window identifier, a list of window manager options, its constituting widgets, and a SimpleAction that is evaluated just after the creation of the window. The latter can be used for initialisation of the widgets in the window, e.g. listboxes or text widgets can be filled with an initial content. Windows are created with the following function:

 type Window
 val mkWindow : {winId : WinId,
 config : WinConfigure list,
 widgets : Widgets,
 bindings: Binding list,
 init : SimpleAction} -> Window

Identifiers for windows can be obtained by the functions newWinId or mkWinId (see section 3.2.1).

The selector functions for Window are:

 val selWindowAction : Window -> SimpleAction
 val selWindowConfigures : Window -> WinConfigure list
 val selWindowWidgets : Window -> Widget list
 val selWindowWinId : Window -> WinId

The type WinConfigure provides a subset of the window manager options for windows, corresponding to the arguments of Tk's wm command (see chapter 22 of [Oust94]):

 datatype UserKind =
 User
 | Program

 datatype WinConfigure =
 WinAspect of int * int * int *int (* xthin/ythin xfat/yfat *)
 | WinGeometry of ((int * int) Option.option) (* width x height *)
 * ((int * int) Option.option) (* xpos x ypos *)
 | WinMaxSize of int * int (* width * height *)
 | WinMinSize of int * int
 | WinPositionFrom of UserKind
 | WinSizeFrom of UserKind
 | WinTitle of string
 | WinGroup of WinId (* window / leader *)
 | WinTransient of WinId Option.option
 | WinOverride of bool

A window title must only contain printable characters (space up to ~). See also the function checkWinTitle in section 3.2.13.

The selector functions for WinConfigure are:

 val selWinAspect : Window -> (int * int * int * int) option
 val selWinGeometry : Window ->
 (((int * int) option) *
 ((int * int) option) ) option
 val selWinMaxSize : Window -> (int * int) option
 val selWinMinSize : Window -> (int * int) option
 val selWinPositionFrom : Window -> UserKind option
 val selWinSizeFrom : Window -> UserKind option
 val selWinTitle : Window -> Title option
 val selWinGroup : Window -> WinId option
 val selWinTransient : Window -> WinId option option
 val selWinOverride : Window -> bool option

3.2. The Signature SML_TK

This section describes the functions from the signature SML_TK. Whereas the data types and functions from the signature TK_TYPES generate the term structure specifying the initial appearance of windows, widgets and other elements of the user interface, the functions from this section are primarily used to control the runtime behaviour of the application.

sml_tk's runtime behaviour is centred around the concept of an event loop. This means that after building the GUI as specified by a list of initial windows (see startTcl below), sml_tk enters a loop waiting for events from the user sent to the graphical display. They are interpreted internally in Tk, passed to sml_tk and then processed by calling ML functions provided by the programmer. In other words, sml_tk is running as a reactive system, since the application merely reacts to user interaction. These reactions can be specified as

Technically, this means that sml_tk keeps track which ML function is bound to which event. The wish is configured to send a string identifying the particular event when it occurs, and sml_tk maps this string to the corresponding ML function, which is then executed.

3.2.1. Names and Identifiers

In Tk, windows and widgets are referred to by their pathnames. In sml_tk, windows and widgets are referred to by unique identifiers. The following operations produce fresh identifiers for windows, widgets, canvas items, frames for widgets within canvas items, text annotations and frames for widgets within >text annotations. The mkXX variants can be used to generate more meaningful names, for the advanced programmer analyzing the logfile (see the appendix).

 val newWinId : unit -> TkTypes.WinId
 val newWidgetId : unit -> TkTypes.WidId
 val newCItemId : unit -> TkTypes.CItemId
 val newCItemFrameId : unit -> TkTypes.WidId
 val newAnnotationId : unit -> TkTypes.AnnId
 val newAnnotationFrameId : unit -> TkTypes.WidId

 val mkWinId : string -> TkTypes.WinId
 val mkCItemId : string -> TkTypes.CItemId
 val mkWidgetId : string -> TkTypes.WidId
 val mkFrameId : string -> TkTypes.WidId

Sometimes it is useful to generate names for widget identifiers from given ones in a reconstructible way. This can be done using the following function:

 fun subWidId(w, str)= w ^ str

The programmer should take great care not to use one identifier for more than one window (widget, canvas item, etc), and in particular should not use identifiers other than those obtained by the above functions.

3.2.2. Starting and Terminating sml_tk

The following four functions control sml_tk's event loop, in particular start and termination of sml_tk:

 val startTcl : TkTypes.Window list -> unit
 val startTclExn : TkTypes.Window list -> string

 val exitTcl : unit -> unit

 val resetTcl : unit -> unit

startTcl takes the main window description and starts the event loop- i.e. it builds and displays the specified list of windows (with all their widgets and subcomponents), and then waits and reacts to user interaction until either exitTcl is called, or the main window is closed.

startTclExn does the same as startTcl, but additionally catches any exceptions which are raised within sml_tk and prints their associated string value, detailing the nature of the failure leading to the exception (see section 3.1.2 above).

resetTcl has to be called before the application can be started again after it was interrupted irregularly, either by a user interruptor by raising an exception. There are two forms of interupts that can be sent by the user to the main window: either <CTRL-C> is pressed which will result in an interuption of the current evaluation of a function bound to an event and continue with the eventloop,or <CTRL-\> pressed which will result in a break of the eventloop and kill the user interface.

3.2.3. Windows

Opening a window with openWindow displays the window on the screen and adds it to the GUI state; for the main window, this is done by startTcl. An attempt to open a window with a window id equal to that of an already open window results in an error. The function occursWin can be used to find out if a window with a specific id has already been opened.

A window is closed with the function closeWindow. Closing the main window results in a call to exitTcl; this is the preferred way to terminate an sml_tk application.

 val openWindow : TkTypes.Window -> unit
 val occursWin : TkTypes.WinId -> bool
 val closeWindow : TkTypes.WinId -> unit

The functions getWindow and getAllWindows can be used the extract information about some or all windows from the GUI state. changeTitle changes the title of the window (as appearing in its title bar).

 val getWindow : TkTypes.WinId -> TkTypes.Window
 val getAllWindows : unit -> TkTypes.Window list

 val changeTitle : TkTypes.WinId -> TkTypes.Title -> unit

3.2.4. General Operations on Widgets

The function getWidget retrieves the widget specified by the identifier WidId from the GUI state.

 val getWidget : TkTypes.WidId -> TkTypes.Widget

Widgets can also be added or deleted dynamically. addWidget adds the widget to the window specified by WinId into the widget specified by WidId. The latter widget has to be a frame widget, and the widget to be added will be inserted at the end of its list of subwidgets. delWidget deletes the specified widget.

An example for the use of addWidget and delWidget can be found in src/tests+examples/big_ex.sml.

 val addWidget : TkTypes.WinId -> TkTypes.WidId -> TkTypes.Widget -> unit
 val delWidget : TkTypes.WidId -> unit

Note that it is not possible to add a widget to a window without any frames in it. In particular, it is not possible to add widgets to an initially empty window- clearly a pathological situation. If you really want a window which initially does not contain any widget, consider using only an invisible frame.

The visual appearance and the behaviour of a widget is modified by the configuration options and binding of the widget. This is is achieved with the following functions: setBind and setConf completely overwrite the bindings or configuration options for the specified widget, whereas addBind and addConf replace bindings or configuration options of the same kind, add new ones and leave the rest untouched.

 val addBind : TkTypes.WidId -> TkTypes.Binding list -> unit
 val addConf : TkTypes.WidId -> TkTypes.Configure list -> unit

 val setBind : TkTypes.WidId -> TkTypes.Binding list -> unit
 val setConf : TkTypes.WidId -> TkTypes.Configure list -> unit

The functions getTextWidWidgets and getCanvasWidgets allow the convenient extraction of all top level widgets within text or canvas widgets; they are undefined for all other variants of widgets.

 val getTextWidWidgets : TkTypes.Widget -> TkTypes.Widget list
 val getCanvasWidgets : TkTypes.Widget -> TkTypes.Widget list

You can also disable certain widgets using the Active configure option, which can also be set when the widget is already constructed (by the addConf or setConf functions). You can apply the Active configure option to buttons, radiobuttons, checkbuttons, menubuttons, scale widgets, entry widgets, and text widgets.

3.2.5. Configuration Options and Bindings for Widgets

The functions in this section are for convenience only. They allow the easy extraction of parts of configuration options or bindings for a widget specified by its WidId.

 (* are all derived from getWidget *)
 val getConf : TkTypes.WidId -> TkTypes.Configure list
 val getRelief : TkTypes.WidId -> TkTypes.RelKind
 val getCommand : TkTypes.WidId -> TkTypes.SimpleAction
 val getBindings : TkTypes.WidId -> TkTypes.Binding list
 val getWidth : TkTypes.WidId -> int
 val getMCommand : TkTypes.WidId -> int list -> TkTypes.SimpleAction

For example, getConf is the composition of getWidget and selWidgetConf.

3.2.6. Operations for Widgets with Text

This section describes some uniform operations for widgets containing text. These encompass mainly list boxes and entry widgets.

A position within text in these widgets is specified with the datatype Mark (see section 3.1.7 above) is used, with the following conventions:

Manipulation of Text

These functions are mainly geared towards list boxes and entry widgets. For text widgets, we recommend the funtions detailed below, since they take annotated texts as arguments, and in particular handle read-only text widgets correctly - note that using insertText etc. on a read-only text widget will fail!

The functions insertText and insertTextEnd insert text into the widget specified by WidId at either the specified position, or the end. The function clearText deletes all text from the specified widget, and deleteText deletes the specified region of text from the specified widget.

 val insertText : TkTypes.WidId -> string -> TkTypes.Mark -> unit
 val insertTextEnd : TkTypes.WidId -> string -> unit

 val clearText : TkTypes.WidId -> unit
 val deleteText : TkTypes.WidId -> TkTypes.Mark * TkTypes.Mark -> unit

Text in widgets of these kinds can be changed interactively by the user. Since this change is not reflected in the GUI state, the following functions have to be used to obtain the actual text (or region of text) from a widget.

 val readText : TkTypes.WidId -> TkTypes.Mark * TkTypes.Mark -> string
 val readTextAll : TkTypes.WidId -> string

Selection of Positions and Ranges

The user may select a position or a range of text within widgets containing text (see section 3.2.15 below). readCursor returns the position of the cursor, and readSelRange returns the regions of the selection as a list of pairs of positions (denoting start and end of the selected regions).

 val readCursor : TkTypes.WidId -> TkTypes.Mark
 val readSelRange : TkTypes.WidId -> (TkTypes.Mark * TkTypes.Mark) list

For example, if wl is a list box, readCursor wl returns Mark(n,_) where n is the index of the list box currently containing the cursor.

Before using the function readSelRange it should be checked with the function readSelWindow (see section 3.2.15 below) that the user has indeed selected some text within the widget.

Operations for Text Widgets

To get and change the current state (read-only or user editable) of a text widget, use the following functions:

 val readTextWidState : WidId -> bool
 val setTextWidReadOnly : WidId -> bool -> unit

To manipulate the contents of text widgets, the following functions are provided. They work on read-only text widgets, and they take annotated texts as arguments, adjusting the annotations in the text as necessary when inserting it.

 val clearAnnoText : WidId -> unit
 val replaceAnnoText : WidId -> AnnoText-> unit
 val deleteAnnoText : WidId -> Mark* Mark-> unit
 val insertAnnoText : WidId -> AnnoText-> Mark-> unit
 val insertAnnoTextEnd : WidId -> AnnoText-> unit (* use discouraged-- very inefficient! *)

The function insertAnnoTextEnd has been implemented in the obvious way, which involves reading the content of the text widget and counting its length, and is far more inefficient that in has to be. If you find you really need this function, consider contributing a more efficient implementation to sml_tk. Until then, restrict its usage to cases where it cannot be avoided.

3.2.7. Canvases and Canvas Items

The following functions are the counterpart to the functions on widgets in section 3.2.4 above for canvas items. They allow the dynamic addition of canvas items to a canvas, their removal from it and the reconfiguration of canvas item bindings and configurations. Canvas Items are identified by the WidId of the canvas within which they appear, and their CItemId identifier.

 val getCItem : TkTypes.WidId ->
                     TkTypes.CItemId ->
                     TkTypes.CItem

 val addCItem :     TkTypes.WidId ->
                    TkTypes.CItem -> unit
 val delCItem :     TkTypes.WidId ->
                    TkTypes.CItemId -> unit

 val getCItemBind : TkTypes.WidId ->
                    TkTypes.CItemId ->
                    TkTypes.Binding list
 val getCItemConf : TkTypes.WidId ->
                    TkTypes.CItemId ->
                    TkTypes.Configure list

 val addCItemBind : TkTypes.WidId ->
                    TkTypes.CItemId ->
                    TkTypes.Binding list -> unit
 val addCItemConf : TkTypes.WidId ->
                    TkTypes.CItemId ->
                    TkTypes.Configure list -> unit

As already noted above the coordinates given to the constructor of a canvas item are only its initial values; the actual values can be read with readCItemCoords. setCItemCoords sets new coordinates, and moveCItem moves an item by the specified distance.

 val readCItemCoords : TkTypes.WidId ->
                            TkTypes.CItemId ->
                            TkTypes.Coord list
 val setCItemCoords :       TkTypes.WidId ->
                            TkTypes.CItemId ->
                            TkTypes.Coord list ->
                            unit
 val moveCItem :            TkTypes.WidId ->
                            TkTypes.CItemId ->
                            TkTypes.Coord ->
                            unit

Note setCItemCoords does not work for CTag canvas items, but moveCItem does. Also, deleting a CTag item does not delete the subitems, only the tag item itself, and adding bindings to a CTag item is somewhat useless, since they are not displayed as such --- you most probably want to add the bindings to the subitems.

The following functions return the height and width of a canvas item in a uniform way. For items of type CIcon with an image content, this is the only way to get their actual width and height. Note that the canvas item has to be displayed on a canvas to use these functions.

 val readCItemHeight : TkTypes.WidId -> TkTypes.CItemId -> int
 val readCItemWidth : TkTypes.WidId -> TkTypes.CItemId -> int

3.2.8. Annotations for Text Widgets

The same set of basic functions as for canvas items is available for text annotations in text widgets. Text annotations are identified by the WidId of the parent text widget and an AnnId identifier.

 val getAnnotation :    TkTypes.WidId ->
                         TkTypes.AnnId ->
                         TkTypes.Annotation

 val addAnnotation :     TkTypes.WidId ->
                         TkTypes.Annotation -> unit
 val delAnnotation :     TkTypes.WidId -> 
                         TkTypes.AnnId ->unit

 val getAnnotationBind : TkTypes.WidId ->
                         TkTypes.AnnId ->
                         TkTypes.Binding list
 val getAnnotationConf : TkTypes.WidId ->
                         TkTypes.AnnId ->
                         TkTypes.Configure list

 val addAnnotationBind : TkTypes.WidId ->
                         TkTypes.AnnId ->
                         TkTypes.Binding list -> unit
 val addAnnotationConf : TkTypes.WidId ->
                         TkTypes.AnnId ->
                         TkTypes.Configure list -> unit

The actual position and regions of annotations can be changed dynamically by the user (by editing the text in the widget); the function readAnnotationMarks reads their actual values. readSelection returns the location of selected text within the particular text widget. Before using this function, it should be checked with the function readSelWindow (section 3.2.15) that the user has really selected some regions in this widget, otherwise the exception TCL_ERROR will be raised.

 val readAnnotationMarks : TkTypes.WidId ->
                                TkTypes.AnnId ->
                                (TkTypes.Mark * TkTypes.Mark) list

 val readSelection : TkTypes.WidId ->
                    (TkTypes.Mark * TkTypes.Mark) list

Caution: Since the user can interactively edit the text, it is possible that annotations are deleted without sml_tk noticing. Hence, you cannot be sure that an annotation, although defined and displayed, still exists, and referring to it may lead to an error. There are three ways to handle this situation:

3.2.9 Annotated Texts

For annotated texts the following utility functions are provided. mkAT makes a string into an annotated text without any annotations, mtAT is the empty annotated text, ++ concatenates annotated texts, keeping track of the annotations. nlAT appends a newline to the annotated text, and concatATWith concatenates a list of annotated texts, interspersing them with the given string.

 val mkAT : string -> TkTypes.AnnoText
 val mtAT : TkTypes.AnnoText

 infix 6 ++
 val ++ : TkTypes.AnnoText * TkTypes.AnnoText -> TkTypes.AnnoText

 val nlAT : TkTypes.AnnoText -> TkTypes.AnnoText
 val concatATWith : string -> TkTypes.AnnoText list -> TkTypes.AnnoText

3.2.10. Menu Widgets

Menus items are widgets, and as such can be added or deleted from a frame (containing other menu items, most likely) by the functions addWidget and delWidget above. Currently, sml_tk does not support the disabling of menus.

Pop-up menus are supported by the following two functions: createAndPopUpMenu takes a pop-up menu widget and pops it up at the specified coordinate, and popUpMenu pops up a menu at the specified coordinate which has already been specified somewhere else in the widget tree. The optional integer parameter is the index of the menu item which should be below the mouse when the menu pops up.

 val createAndPopUpMenu : TkTypes.Widget ->
                            int TkTypes.option ->
                            TkTypes.Coord ->
                            unit

 val popUpMenu : TkTypes.WidId ->
                 int TkTypes.option ->
                 TkTypes.Coord -> unit

src/tests+examples/popup_ex.sml is an example for the use of popup menus.

3.2.11. Buttons and Tcl Values

As already noted in section 3.1.7 above, these two functions are needed in the context of check buttons or radio buttons. The first argument is the variable name, the second argument for setVarValue is the intended value.

 val setVarValue : string -> string -> unit
 val readVarValue : string -> string

3.2.12. Coordinates

Coordinates are pairs of integers. They can be added or subtracted with addCoord and subCoord; smultCoord is multiplication with a scalar.

 type Coord= int* int
 val addCoord : TkTypes.Coord -> TkTypes.Coord -> TkTypes.Coord
 val subCoord : TkTypes.Coord -> TkTypes.Coord -> TkTypes.Coord
 val smultCoord : Coord-> int-> Coord

A rectangle is given by two coordinates, specifying two of its corners. inside p r returns true if the point at coordinate p is inside the rectangle r, and intersect checks wether the two rectangles intersect. moveRect moves a rectangle by the specified coordinate (considered as a vector).

 type Rect = Coord* Coord
 val inside : TkTypes.Coord -> Rect -> bool
 val intersect : Rect-> Rect-> bool
 val moveRect : Rect -> TkTypes.Coord -> Rect

Finally, show functions are provided as a convenience.

 val showCoord : Coord list -> string
 val convCoord : string -> Coord list
 val showRect : Rect -> string

3.2.13. Checks

val checkWidId    : WidId -> bool
val checkWinId    : WinId -> bool
val checkWinTitle : TkTypes.Title -> bool

These functions checks whether the argument is a correct window id, widget id or window title.

Implicit checks of the widget configurations have been added. The applied configurations are checked, when the widgets are packed. An exception WIDGET will be raised, when bad configure options are found. However, sml_tk's checks are not as comprehensive as one might hope for. The exception TCL_ERROR is raised when the wish returns a Tcl error- this happens in particular if an illegal window (widget, canvas item, etc.) is passed to the wish.

3.2.14. Focus and Grabs

The functions focus and deFocus set and reset the keyboard focus to the specified window. Please refer to chapter 21 in [Oust94] for an explanation of the Tk focus model.

 val focus : TkTypes.WinId -> unit
 val deFocus : TkTypes.WinId -> unit

With grab and deGrab, modal interactions can be implemented in sml_tk (see chapter 24 of [Oust94]). A grab "claims ownership" of the mouse and keyboard, such that all subsequent events will go to the claiming window. A particular example of this is a a window containing a dialog box the user is required to answer before proceeding.

 val grab : TkTypes.WinId -> unit
 val deGrab : TkTypes.WinId -> unit

Be careful to alway release a grab with deGrab; failing to do so can lead into unpleasant situations. To prevent the worst case (completely freezing up the display) global grabs are not provided by sml_tk, i.e. all grabs are local.

3.2.15. The Selection

In window systems such as X windows, the user can select regions of text by holding a mouse button and moving the mouse over the region of text to be selected. This selection is display-wide, and is called the X selection below. An application is said to own the selection if a region in one of its windows is selected. The function readSelWindow returns NONE if the application does not own the X selection, and the identifiers of the window and widget which own the X selection otherwise. Within text widgets, list boxes and entry widgets, the X selection can be accessed with the readSelRange

 val readSelWindow : unit -> (TkTypes.WinId * TkTypes.WidId) TkTypes.option

Chapter 20 in [Oust94] explains the X selection in depth.

3.2.16. Interrupt Handling

To handle interrupts (CTRL-C), sml_tk uses a very simple-minded broadcast model. Applications can register so-called listeners, which are functions which are called when an interrupt occurs (i.e. the user aborts an ongoing computation by hitting CTRL-C). Such a listener is given by the abstract data type intr_listener, and about the only thing we can do with that is de-register it again (which means, of course, that it isn't called anymore when an interrupt occurs). Typically, a listener will ensure an application's state consistency - note that interrupts can occur anywhere inside a function, so stateful computations may need to reset the state to a consistent value.

type intr_listener

 val registerIntrListener : (unit-> unit)-> intr_listener
 val deregisterIntrListener : intr_listener-> unit

Note that the user can also always type CTRL-/ to abort sml_tk altogether and return to the SML toplevel. If you want to change this behaviour (or the keys they are bound to), have a look at src/njml.sml.  Note: this behaviour may not work correctly with all SML compilers (though it does with SML/NJ).

3.2.17. sml_tk Initialization and Configuration

The init function initializes sml_tk: it sets the basic configurations of sml_tk from the environment as explained in section, and initializes the fonts and the terminal. It needs to be called before any sml_tk application is started. You can also initialize the fonts separately and unconditionally (init only initializes the fonts if the environment variable DISPLAY has changed.)

 val init : unit -> unit
 val initFonts : unit -> unit

The following functions can be used to read or update the current values of the runtime configuration variables (see ). Note that updates will be overwritten by the next call to init. The only two interesting functions here will probably be getLibPath to get the location of image files etc, and updLogfilePath to switch on logging for one debug run of the system (see the appendix).

 val getLibPath : unit -> string
 val updLibPath : string -> unit
 val getTclPath : unit -> string
 val updTclPath : string-> unit
 val getLogfilePath : unit-> string
 val updLogfilePath : string-> unit

4. The Toolkit Library

The toolkit library offers two collection of modules which are not part of the core of sml_tk, but offer additional functionality. The first collection is called Common Infrastructure (CI) and comprises utilities, abstract events, exchange mechanisms between gui-components and common intercaces for visualizable elements that are used and exchanged between various more astract gui-components. The second collection called Gui Components (GC) and contains a number of preconceived (and sometimes generic) widgets and windows for a number of routine tasks in the implementation of gui's.

In more detail, the Common Infrastructure contains:

Based on CI, the Gui Components comprise the following:

The next five sections are concerned with the discription of CI, while the rest of the chapter is devoted to the description of Gui Components.

4.1. Exchanging Objects: the Clipboard

The clipboard module allows to interchange objects between different applications, like the filer and GenGUI.

Its basic idea is that applications can put objects into the clipboard, and other applications can get the objects previously put into the clipboard. The actions of putting and getting are certified by a TkEvent, and a subsequent get only retrieves the object if the cursor root positions of the certifying events match. For example, a put can be triggered by releasing the drag button outside the window, and the get can be triggered by the cursor entering the window. Then we would only want the get to retrieve the put object if the cursor has not moved in between, i.e. the root position of the TkEvents are equal.

The signature of the clipboard reads as follows:

signature CLIPBOARD =
 sig
 type obj

 exception Empty

 val get: TkTypes.TkEvent-> obj
 val copy : TkTypes.TkEvent-> obj
 val put: obj-> TkTypes.TkEvent -> (unit-> unit)-> unit

 val isEmpty: TkTypes.TkEvent -> bool
 end

get is as described above. The third argument to put is a call-back function, which is called if a subsequent get for the object succeeds. This can be used to ``pass'' objects from one application to another - the call-back function would delete the object from one application once another application has successfully taken it out of the clipboard. copy takes the object out of the clipboard without calling the call-back; hence if the callback is used to delete an object once it appears elswhere, by calling copy we would copy the object, rather than move it. isEmpty is true if a subsequent get (or copy) with the same event would be successful. An unsuccessful get or copy (either because there is no object, or because the two events do not match) will raise the exception Empty. Further, any unsuccessful get or copy will empty the clipboard.

As an example, consider the way the filer and GenGUI interchange objects. The filer is a functor which has as its argument the clipboard, and two functions converting files and directories into clipboard objects (because it depends on the application GenGUI is instantiated with how to convert a file name or directory name into an object):

functor ClipFiler
 (structure M : sig val filter_files : string -> string -> bool;
 exception bad_regexp
 structure CB: CLIPBOARD
 val fileToObj : string* string-> CB.obj
 val dirToObj : string-> CB.obj
 end): FILER_SIG

(At least, thuThe filer will call CB.put whenever the user releases the mouse button after a file selection. (By the peculiarities of Tk's event handling, even if the cursor has been moved over another window in the meantime, the Release event will still go the filer.)

Any application instantiating the GenGUI has to have a clipboard substructure:

signature APPL_SIG =
 sig
 [...]
 structure CB : CLIPBOARD
 sharing type CB.obj = unit -> object list
 end

Whenever a cursor enters the construction area, a CB.put is tried. If the cursor position matches the previous CB.get, then the cursor has not moved in between: this means that the Enter event has occurred immediately after a button release. (Note that if the mouse is moved into the construction area with the mouse button pressed, no Enter event is generated, only once the mouse button is released.) If the CB.get is successful, the user has dragged a file from the filer into the construction area, and GenGUI will have the object appear at that position. Note how we pass a closure rather than the actual object in order to avoid the unnecessary creation of objects.

Note the the type sharing equation above is not legal according to the SML97 standard, hence the real signature looks a bit more complicated.

Finally, the clipboard has two sub-signatures, CLIPBOARD_R and CLIPBOARD_W, which are for read-only and write-only access to the clipboard. For example, in the above the argument of the ClipFiler has actually write-only access to the clipboard, so the line reads

structure CB: CLIPBOARD_W 

4.2. Managing Icons

Some gui-components such as GenGUI associate an icon with every object, depending on its type and mode. The application has to implement these icons, assigning an icon to every object type and mode with the function icon: objtype* mode -> Icons.icon. The icon has to be implemented by the application, using the structure Icons in the toolkit library, which has the following export signature:

signature ICON_SIG =
 sig type icon

 val getIcon : string * string -> icon

 val selWidth : icon -> int
 val selHeight : icon -> int

 val selImage : icon -> SmlTk.IconKind
 val selHiLite : icon -> SmlTk.IconKind
 val selOutline : icon -> SmlTk.IconKind
 val selMicroline : icon -> SmlTk.IconKind

 exception ICON end

Every icon comes in four varieties: normal, highlighted, outlined and microlined (i.e. wee). The highlighted image is displayed to signal that an object is ready to receive a drag&drop operation (see above), the outlined image is displayed when an object is not ready to receive any kind of operation, the microlined object is used in the tree navigation component, and the normal image is presented at all other times.

The present implementation of icons assumes that all icons are Tk file images (i.e. graphical formats such as GIF readily understood by the wish). To create an icon, the function getIcon is called with the directory the file image is in, and its file name. The highlighted and outlined images are supposed to be in the same directory, with the base file name of the normal image suffixed with -hi, -out and -mic respectively (followed by the file name extension). They are also supposed to be of the same size as the normal image. Further, a data file with the same file name but the extension data has to exist which contains, in two lines, the width and height of the icon. (This is due to a catch 22 in the image handling of Tcl/Tk and GenGUI - essentially, we cannot know how large an image without displaying it, but we since we want to place it on the notepad, we need to know how large it is before we do that.)

For example, an application could have types of objects called wotsits and assign icons to them by the following function declaration

fun icon wotsits = getIcon("/home/me/example/icons", "wotsit.gif")
 | icon ... = ...

Then the directory /home/me/example/icons has to include the files wotsit.gif, wotsit-hi.gif, wotsit-out.gif>, wotsit-mic.gif and wotsit.data. If all of wotsit*.gif are GIF images sixty pixels wide and forty pixels high will consist of only the two lines

60
40

(Note that GIF images are a licensed trademark of Unisys, Inc.) Finally, the icon used to represent the trashcan is determined by the configuration as in the next section.

4.3. The Markup Language Parser

Once you've written down two or three annotated texts, you very easily get bored at having to count lines and rows in order to get your annotations in the text. In order to remedy this situation, sml_tk provides a markup language, and a generic parser for it.

The fully generic markup language parser is a functor takes as an argument a structure describing tags. A tag is an SGML element of the form

 <tagname arg1 ... argn> ... </tagname>

This generates an annotation from the start of the tag to the end of the tag. Tags are given by matchingTag, where the first argument is the tagname above. The annotation is generated by the function annotationForTag, which is passed the tag as the first argument, and the list of arguments as the second argument. The type widgetinfo is just a workaround to allow annotationForTag to be passed arguments like the identity of the widget.

The markup parser further recognizes escape sequences of the form &name;. These are given by the function escape, which is passed the name of the escape sequence, and either returns a string replacing the escape, or NONE in which case the escape sequence is left as is in the text. Three escape sequences are predefined, namely &amp; for &, &gt; for > and &lt; for < (since these are needed by any sensible markup language).

Finally, the exception AnnotationError can be raised by the function generating to annotation to indicate an error (e.g. a tag with not enough parameters). The exception error is raised if there is a critical error during parsing. The parser is actually quite good-natured, and will e.g. only print warnings (via warning) if there are open elements which do not close (for these, annotations up to the end of the text are generated). The only critical errors are < and & not followed by any > or ; respectively.

signature TAGS =
 sig
 type tag
 type widgetinfo

 val matchingTag : string-> tag option
 val annotationForTag : tag ->string list-> widgetinfo->
                        (TkTypes.Mark* TkTypes.Mark)->
                        TkTypes.Annotation
 val escape : string-> string option

 exception AnnotationError of string
 val warning : string-> unit
 val error : string-> exn
 end

The markup language parser exports just one function, which takes a text in a markup language as described by the argument, and returns an annotated text:

signature SMLTK_MARKUP =
 sig
 type widgetinfo
 val getAnnText : widgetinfo-> string-> TkTypes.AnnoText
 end

functor SmlTkMarkup (Tags: TAGS) : SMLTK_MARKUP
 where type widgetinfo= Tags.widgetinfo

The file src/toolkit/tests+examples/markup_ex.sml contains an example of a small self-defined markup language. Moreover, the toolkit library contains an extendible markup language, the sml_tk Standard Markup Language, which provides tags to set fonts, scale fonts, or raise and lower boxes, and in particular a rich set of escape sequences for mathematical characters and mathematical notations (&alpha;, &forall;, &tensor; etc.) The file src/toolkit/tests+examples/stdmark_ex.sml shows how to use the standard markup language. It comes in two variations, one which is extendible (functor StdTags) with more tags, and one which is closed and ready to use (structure StdMarkup).

4.4. A Common Interface for Visualizable Elements: Objects

Several higher toolkit components require a common notion (or: format) of objects for their visualization functionality - this notion is represented by the signature OBJECT_CLASS that is part of the input signature of the functors representing these higher Toolkit Components. Thus, OBJECT_CLASS is fundamental for toolkit components like TreeList or GenGui. Morover, there is a functor obj2tree_obj, that extends OBJECT_CLASS to TREE-OBJECT_CLASS, where TREE-OBJECT_CLASS is a strict signature extension of OBJECT_CLASS. The instances of TREE-OBJECT_CLASS all have a tree-like structure and are suited for the representation of file-systems, proof-terms, etc. Once we have one notion of objects, obj2tree_obj builds a tree-like organization with folders as nodes and input objects as leaves. This facilitates the construction and data exchange via uniform interfaces and common lifting facilities for toolkit-components with respect to objects.

OBJECT_CLASS and its variants are part of the Common Infrastructure CI. In more detail, OBJECT_CLASS instances contain the following elements:

The signature in itself reads as follows:

     signature OBJECT_CLASS =
     sig
        type object
        eqtype objtype
        type name                                     (* think of it as : id *)

        val  ord            : object * object -> order(* based on name *)
        val  name_of        : object -> name          (* think of it as : id_of *)
        val  string_of_name : name ->   Print.format ->
                                        string       
        val  rename         : string ->              
                                        object -> unit(* side effect *)
        val  reset_name     : object -> unit             (* side effect *)
        val  obj_type       : object -> objtype
        val  icon           : objtype -> Icons.icon
     end

More formally, these elements are specified to have the following properties:

  1. ord is a linear ordering on names of objects
  2. name_of(o) must be unique in all system states
  3. (* this fact is only used in tree_object_classes,
    more precisely: select_from_path,remove_at_path,update_at_path *)
  4. name_of(rename(s,o)) = name_of(o)  (* rename is actually a relabelling *)
  5. name_of(reset_name(s,o)) = name_of(o)
  6. obj_type(rename(s,o)) = obj_type(o)
  7. obj_type(reset_name(s,o)) = obj_type(o)
  8. icon(rename(s,o)) = obj_type(o)
  9. icon(reset_name(s,o)) = obj_type(o)
  10. string_of_name n f should be "as nice as possible".
  11. (rename s o; string_of_name(name_of o) f) = "nice s"
  12. (rename s o; reset_name o; string_of_name o f) =
    (reset_name_node o; string_of_name_node o f)



In the following. we turn to the extension of OBJECT_CLASS, namely TREE-OBJECT_CLASS. The entity TREE_OBJECT_CLASS is a subclass of OBJECT_CLASS. It is enriched by FOLDER_INFO, SUBNODE_INFO and functions that exploit (or enforce) the tree-like structure of TREE_OBJECT_CLASS-elements, i.e. terms of type obj. Additionally, they provide the concept path on folders and path-related operations. The auxilliary signatures FOLDERINFO contains the information that makes abstractly the skeleton or just the node of a folder, but not its content. This node_info must contain the following:

Now, FOLDERINFO reads as follows:

     signature FOLDERINFO =
     sig
        type   node_info
        type   subnode_info
        val    string_of_name_node : node_info -> Print.format -> string
        val    ord_node            : node_info * node_info -> order
        val    rename_node         : string -> node_info -> unit
        val    reset_name_node     : node_info -> unit
     end

Some properties are specified as follows:

The signature TREE_OBJECT_CLASS is constructed on top:

     signature TREE_OBJECT_CLASS =
     sig
        include OBJECT_CLASS;
        include FOLDERINFO;
        structure Basic : OBJECT_CLASS;
        val  getContent       : object -> Basic.object * subnode_info
        val  getFolder        : object -> node_info * object list
        val  isFolder         : object -> bool
        val  Content          : Basic.object * subnode_info -> object
        val  Folder           : node_info * object list -> object
        val  isFolderType     : objtype -> bool
        val  getContentType   : objtype -> Basic.objtype
        val  ContentType      : Basic.objtype -> objtype
     end

Its specification contains the following conditions:

A variant of TREE_OBJECT_CLASS is the signature PTREE_OBJECT_CLASS, which extends the former by path-related operations.

     signature PTREE_OBJECT_CLASS =
     sig
        include TREE_OBJECT_CLASS;

        type path             = node_info list * Basic.object option
        (* path and name are identical in PTREE_OBJECT_CLASS. Unfortunately,
         * this can't be said explicitly in SML. Therefore, we establish an
         * isomorphism. *)

        val  ord_path         : path * path -> order
        val  is_prefix        : path * path -> bool
        val  concat_path      : path * path -> path

        val  name2path        : name -> path
        val  path2name        : path -> name

        (*   The foll. opns may fail if paths do not exist or are not unique   *)
        (*   NOTE : this implies that node_info and obj must be unique if      *)
        (*   these operations are expected to work properly *)
        val  get_path         : object -> object -> path list
                                (* get_path a b produces path
                              of sub-object b in object a *)
        exception InconsistPath;

        val  select_from_path : object list -> path -> object

        val  remove_at_path   : object list -> path -> object list
                                (* removes_at_path a   produces object
                                   from a with subobject at p removed *)

        val  update_at_path   : object list -> path -> object -> object list

     end

A standard method to lift instances of OBJECT_CLASS to PTREE_OBJECT_CLASS (and thus TREE_OBJECT_CLASS) is given by the functor obj2tree_obj, that embeds standard objects into a tree-like data structure:

     functor obj2tree_obj (structure N:FOLDERINFO and
                                     M:OBJECT_CLASS) : PTREE_OBJECT_CLASS

4.5. A Common Interface for the Data Model: Applications

The sml_tk-Toolkit provides two larger parametric components, that produce for a given application a graphical user interface, slightly similar to the model-view-controller paradigm known from object-oriented GUI programming, where our application corresponds to the model. Technically, these two parametric components, GenGui and TGenGui, are implemented as functors that map applications to widgets that contain the GUI for this application. Applications are part of the Common Infrastructure CI.

In the following, we represent a family of application signatures NP0_APPL_SIG, NP_APPL_SIG and APPL_SIG that represent the interface into which a given application must be wrapped in order to be accomodated for this functor.

An application essentially contains:

In more detail, the discussed signatures look as follows:

ignature NP0_APPL_SIG =
  sig

    include OBJECT_CLASS

    (* New objects are objects together with an annotation where
     * they should appear.  This is a coordinate followed by an Anchor
     * which gives the direction in which GenGUI tries to place the
     * object if another object is in the way.
     * new_object will correspond directly to Contents in TreeObjects.
     *)
    type new_object = object * (SmlTk.Coord* SmlTk.AnchorKind)

    (* Now comes the GenGUI-specific OBJECT_CLASS extensions:
       Typing, modes, is_constructed, outline. *)
    (* Typing *)

    val objlist_type : object list -> objtype Option.option

    val is_constructed : objtype-> bool
                        (* objects of this type are construction objects *)
 

    (* "Modes" are states for objects. They are changed with the object's pop-up
     * menu, which displays the mode by the mode_name given below.
     * Every object's mode can be set within the range given by its type
     * (function modes below) by set_mode.
     * Every object's mode can be set within the range given by its type (function
     * modes below) by set_mode.
     *)
    eqtype mode

    val mode      : objtype       -> mode   (* New ! mode is attached to objtype
                                               for structuring reasons . . . *)
    val modes     : objtype       -> mode list
    val mode_name : mode          -> string
    val set_mode  : object * mode -> unit

    (* These objects are displayed with an "outline" icon, to indicate
     * some out-of-date condition. Note that they can still receive
     * drag-and-drop operations.
     *)
    val outline      : object-> bool

    (*
     * Nullary objects are constants, or in other words, objects
     * existiting a priori.
     * The init function returns a list of all these objects; it will
     * only be called once, on startup.
     *)
    val init   : unit -> new_object list

    (* Unary operations *)

    (* standard actions, called ops for historic reasons *)
    val std_ops       : objtype-> ((object -> unit) * string) list
                                           (* better signature ? *)

    val create_actions: (({pos : SmlTk.Coord, tag : string} -> unit)
                         * string) list
    val label_action  : {obj : object,
                         cc : string -> unit}-> unit
    val delete        : object -> unit
 

    (* further object type specific operations: for a type t, monOps t
     * is a list of pairs (f, s), where f is a unary operation, and s
     * is a string, the name under which it appears in the pop-up
     * menu. f has the functionality
     *     object* SmlTk.Coord-> (newObject-> unit)-> unit;
     * where the first argument is the object itself, together with its present
     * location, and the second argument is a continuation you can use
     * to create new objects.
     *)
    val mon_ops : objtype ->
                 ((object * SmlTk.Coord ->
                   (new_object ->  unit) -> unit) * string) list

    (*
     *
     * binary operations
     *
     * aka.the drag&drop-action-table
     *)
    val bin_ops :  objtype * objtype -> (object * SmlTk.Coord *
                                         object list *
                                         (new_object-> unit) -> unit)
                                                                Option.option

    (* --- Substructures -------------------------------------------------- *)  

     (* The clipboard will allow the e