1027 lines
46 KiB

% ISOTAS'96 paper
% Author: Erick Gallesio []
% Creation date: 20-Jun-1995 11:54
% Last file update: 1-Dec-1995 14:57
% Command definitions
\newcommand{\stk}{\mbox{\sc STk}}
\newcommand{\stklos}{\mbox{\sc STklos}}
\newcommand{\rrrr}{{\em R$^{4}\!RS$}}
\title{Designing a Meta Object Protocol to Wrap a Standard Graphical Toolkit}
\author{Erick Gallesio}
\institute {Universit\'e de Nice~~-~~Sophia~Antipolis \\
Route des Colles - B.P. 145\\
06903 Sophia-Antipolis Cedex - FRANCE\\
This paper presents a graphical package which relies on the Tk toolkit and
the Scheme programming language. The Tk package is a widely used graphical
toolkit built upon the Tcl scripting language. Tcl was not designed as a
general purpose programming language and its usage for large-scale software
development is generally not suitable. To improve the programming level of
the Tk toolkit, we have defined {\stklos}, a Scheme language with a
CLOS-like object system. This alternative language has been used to embody
the standard Tk widgets in a clean hierarchy of classes, which is presented
here. The {\stklos} object system implementation is based on a Meta Object
Protocol; this protocol and its usage for accessing the Tk toolkit in an
efficient way are also presented here.
The Tk package \cite{Ouster-book} is a widely used graphical toolkit which
provides a large set of widgets such as buttons, scrollbars, menus or text
editors. With these high level widgets, one can build rather complex
interfaces with little effort and without coping with the usual intricacies
needed when programming under the X window system {\cite{X11}}. The Tk
toolkit relies on a small interpretative scripting language named Tcl (Tool
Command Language)~\cite{Ouster-Tcl}, a string based language with a
shell-like syntax.
Tcl is a small scripting language. To easily embed the Tcl interpreter
in application programs, some usual programming languages capabilities
have been set aside by its author. In particular, Tcl has a poor set
of data structures reduced to character strings and associative
arrays. It provides numbers, which are simulated with strings and
which are, consequently, slow. In fact, a proper usage of Tcl consists
in writing small scripts to {\em glue} large application components
written in C or C++. However, experience shows that people are
reluctant to use it in this way, and that they often prefer to write
applications with a single programming language.
Tk is indeed an application with an embedded Tcl
interpreter~\cite{Ouster-Tk}. The interpretative nature of Tcl
provides Tk a simple and attractive interface to develop simple
graphical programs. However, the easiness afforded by Tcl for the
design of small interfaces is misleading and it often encourages
people to start heavy developments with this language. But, writing
large applications with a language which lacks ways of structuring
data tends to be more and more painful as the program grows. We think
that this kind of usage is beyond the scope of a language such as Tcl,
and we have tried to propose a solution for a better using of the
Tk toolkit.
In order to improve the Tk toolkit for large applications, we decided
to replace Tcl by a conventional programming language. Furthermore,
the {\em substitute} language must fulfill some important
requirements; it must be
\item {\bf high level} and provides useful data types such as
structures, arrays, lists or strings
\item {\bf small} enough for allowing to embed it in
applications (as Tcl)
\item {\bf efficient} so that most applications can be written
entirely in this language without having to resort to C or C++ programming.
\item easily {\bf extensible} so that user can investigate several
interesting programming paradigms ({\em e.g.} objects, prototypes,
\item already {\bf defined}. This point is, of course, not mandatory, but we
think that it is preferable to use an existing programming language, if
possible, rather than defining a new one.
Scheme \cite{R4RS} is a Lisp dialect which satisfies quite well the
previous points. It is a statically scoped language with a clear and
simple semantic. Moreover, Scheme procedures are first class objects
able to capture their creation environment. This language feature is
important since it allows us to envision the coding of interfaces {\em
callbacks} in a clean way. In this framework, we have defined
{\stk}~{\cite{Gallesio93-1}, a graphical package based on Tk toolkit
where the Tcl language as been replaced by a Scheme interpreter.
{\stk} is a small and efficient Scheme interpreter. As Tcl, it is
small enough to be used simply as a {\em glue language} which can be
embedded in an existing application. Furthermore, the solid basis
provided by the Scheme language affords the tools necessary for
writing, and maintaining medium size Graphical User Interfaces
(GUI). Nevertheless, we think the expressive power of Scheme is not
sufficient to envisage its use for large-scale software development.
In particular, the lack of an object mechanism increases the
programming complexity of large applications. {\stklos}, the object
extension of {\stk}, has been defined to alleviate this problem. This
extension provides meta classes, multiple inheritance and generic
functions {\em \`a la} CLOS~\cite{CLOS,CLtL2} or Dylan~\cite{Dylan}.
{\stklos} has also been used to embody the predefined Tk widgets in a
hierarchy of classes. Usage of these classes simplifies the core Tk
usage by providing an homogeneous access to widget options and by
hiding the Tk widgets low level idiosyncrasies. Moreover, as expected,
usage of objects facilitates code reuse and definition of new widgets
classes. Finally, we think that the object orientation of {\stklos},
as well as the solid basis of the Scheme programming language, afford
therefore the tools necessary to envision writing, and maintaining,
complex GUI.
The rest of this paper is divided in three sections. The next section
presents the {\stk} package and its object system. Wrapping the
standard Tk widgets in {\stklos} classes and the influence of this
integration in interfaces programming are described in section 3.
{\stklos} implementation relies on a MOP (Meta Object Protocol), in
the spirit of the one defined for CLOS~\cite{AMOP}. Section 4 presents
this protocol and how it has been used to integrate the Tk standard
widgets in the Scheme world.
\section{Presentation of {\stklos}}
Programming with {\stk} can be done at two distinct levels. The first
level uses only the standard Scheme constructs and is quite
classical. The second level gives access to {\stklos}, the object
oriented extension of {\stk}. Of course, both levels can be used at
the same time in a single program. However, most of the time, one will
use the higher level, resorting to the lower one for specific purposes
\subsection{\stk: the Basic Layer}
Starting a session with {\stk} brings the user in the
basic layer which gives access to an extended Scheme interpreter
able to handle the Tk toolkit. With a little set of rewriting
rules from the original Tcl/Tk library, and the Tk manual pages close
at hand, one can easily build a {\stk} program using the Tk toolkit.
Creation of new widgets (button, label, canvas, \ldots) is done by
special {\stk} primitives procedures. For instance, creating a new
button can be done as followed
(button '.b)
Tk uses a very special way to name widgets: a widget name is a kind
of pathname which reflects its position in
the graphical hierarchy of widgets. In this example, the name of the
newly created button is ``{\tt .b}''. This pathname states that ``{\tt
b}'' is a son of ``{\tt .}'', the root window. Note that the name of
the widget must be {\em quoted} due to the Scheme evaluation
Calling a widget creation primitive, such as {\tt button},
builds a new Scheme object which is called a {\em Tk-command}. This
object, which is considered as a new basic type by the {\stk}
interpreter, is automatically stored in a variable whose name is the
symbol given to the creation function ({\tt .b} in this case). A
Tk-command is a special kind of function which is generally used, as
in Tcl/Tk, to customize its associated widget. For instance, the
(.b 'configure :text "Hello, world" :border 3)
allows us to set the text and background options of
the~{\tt .b} button. Of course, as in Tcl/Tk, parameters can be passed
at widget creation time, and the previous button creation and
initialization could have been done in a single expression, such as
(button '.b :text "Hello, world" :border 3)
Tk proposes a general purpose binding mechanism to associate
a handlers to an external event ({\em e.g.} a key press or a mouse
action). An event handler is automatically triggered by the library
when the given event occurs. In Tcl, an event handler is a string which is
evaluated at the global level, whereas in {\stk} it is a Scheme
closure. The following expression adds a new event handler to the {\tt
.b} button when the third mouse button is depressed over it:
(bind .b "<ButtonPress-3>"
(let ((count 0))
(lambda ()
(set! count (+ count 1))
(format #t "# of button press: ~A~%" count))))
This simple example shows that {\stk} handlers are cleaner than Tcl ones:
the standard Scheme lexical scoping allows a handler to have its own
private global variables (as {\tt count} here); on the other hand, a Tcl
handler is a flat string unable to carry an environment.
Even if closures afford a better expressive power for writing event
handlers than Tcl strings, programming an interface resorting only to the
constructions of the basic layer becomes rapidly tedious. In fact, the
{\stk} basic layer can be considered as a kind of {\em assembly language}
for interfaces programming and we will see in section~\ref{reification} how
it can be used for the {\em reification} of the Tk widgets in {\stklos}
\subsection{\stk: the Object Layer}
{\stklos}, the object extension of {\stk}, is close to the CLOS
system~\cite{CLOS}; it is briefly introduced in this section. Note that we
consider only the language aspects of {\stklos} here and we
forget its use for integrating the Tk toolkit for a while.
Definition of a new class is done with the {\tt define-class} macro. For
(define-class Point ()
((x :init-keyword :x :accessor x-of)
(y :init-keyword :y :accessor y-of)))
defines the characteristics of a point. Two slots are declared here: {\tt
x} and {\tt y}.
Creation of new instances is done with the {\tt make} constructor:
(define p (make Point :x 10 :y 20))
The evaluation of the preceding form builds a new point and initializes
its slots {\tt x} and {\tt y} with the values 10 and 20. Slot content
can be accessed by the two basic primitives {\tt slot-ref} and {\tt
slot-set!}. These primitives are low level primitives and users often
prefer to use accessors, since they generally lead to a clearer
code. For instance, getting the value of the {\tt y} slot of {\tt
p} could be done in the following way:
(y-of p) {\em ; or (slot-ref p 'y)}
since the {\tt y-of} accessor has been defined for slot {\tt
y}. This slot can be set by the generalized {\tt set!}\label{set!}
form, as illustrated by the following example:
(set! (y-of p) 1) {\em ; or (slot-set! p 'y 1)}
Now, we can define the {\tt Rectangle} class which inherits from the {\tt
Point} class:
(define-class Rectangle (Point)
((width :init-keyword :width :accessor width-of)
(height :init-keyword :height :accessor height-of)))
The instances of this class have four slots ({\tt x}, {\tt y}, {\tt width}
and {\tt height}). Methods\footnote{In {\stklos}\cite{Gallesio95-1a}, the
execution of a method rely on a subset of the CLOS {\em generic functions}
mechanism (only primary methods are supported and the methods combination
cannot be changed).} defined for instances of the {\tt Point} class can
also be used for instances of the {\tt Rectangle} class. For example, the
{\tt x} coordinate of a {\tt Rectangle} can be accessed with the accessor
method {\tt x-of} defined before.
Previous class definition represents rectangles with a reference point, a
width and a height. This representation for rectangles is, most of the
time, convenient but we sometimes need a representation using the
coordinates of two opposite corners. In that case, {\em virtual slots} can
be used\label{virtual-slot}. A virtual slot is a slot which is defined as a
normal slot but whose allocation is declared as {\tt :virtual}. Such a slot
has a null allocation and its reading (resp. writing) provokes the
execution of a getter (resp. setter) function which must be provided by the
user within the class definition. The getter and setter functions are
defined with the {\tt :slot-ref} and {\tt :slot-set!} options. Here is
another writing of the {\tt Rectangle} class using virtual slots:
(define-class Rectangle (Point)
((width :init-keyword :width :accessor width-of)
(height :init-keyword :height :accessor height-of)
(x2 :init-keyword :x2 :accessor x2-of
:allocation :virtual
:slot-ref (lambda (obj) (+ (x-of obj) (width-of obj)))
:slot-set! (lambda (obj val)
(set! (width-of obj) (- val (x-of obj)))))
(y2 :init-keyword :y2 :accessor y2-of
:allocation :virtual
:slot-ref (lambda (obj) (+ (y-of obj) (height-of obj)))
:slot-set! (lambda (obj val)
(set! (height-of obj) (- val (y-of obj)))))))
In this new definition of {\tt Rectangle}, {\tt x2} and {\tt y2} are
virtual slots. The getter and setter associated functions are
lambda expressions which compute or set their value depending on
other slots value. Note that a virtual slot accessor closure can
change the value of standard slots in order to keep the system
Since virtual slots do not imply memory allocation, they could easily be
simulated with classical accessor methods. But, declaring a slot as
virtual allows introspecting functions to ``see'' it as a standard slot. On
the contrary, using a couple of methods to simulate such a slot would hide
it to these functions.
\section{Integration of Tk widgets}
\subsection{The Class Hierarchy}
This section presents how the standard Tk widgets have been embodied in
{\stklos} classes. Each graphical object defined in the Tk toolkit such as
menu, label or button is represented by a {\stklos} class. The
corresponding classes constitute a hierarchy which is briefly described
here. First, all the classes share a unique ancestor: the {\tt <Tk-object>}
class\footnote{End users will not have to use direct instances of the {\tt
<Tk-object>} class (all classes whose name begins with the ``Tk-'' prefix
are abstract classes which should not be instanced; they correspond to the
{\em implementation specific} classes of
\cite{Kickzales:oopsla92}).}. This class defines a set of slots
necessary to establish a communication between the Scheme and Tk
worlds. In particular, two slots are defined in this
class\footnote{The actual implementation is more complex, but to make
easier the reading of this paper, we have simplified the definition of
classes, and hence the class hierarchy.}:
\item The {\tt parent}\label{parent-slot} slot contains a
reference to the object which (graphically) includes the current object.
\item The {\tt Id} slot contains a reference to the low level {\stk} {\em
Tk-command} which implements the {\stklos} widget. This {\em Tk-command},
which is different for each class, is created during {\stklos} instance
initialization. This slot establishes the link between the {\stk} and the
{\stklos} layers and guarantees, by keeping a reference to the low level
widget, a protection against GC recovery.
The next level of the class hierarchy defines a fork with two
branches: the {\tt <Tk-widget>} class and {\tt <Tk-canvas-item>}
class. Instances of the former class are classical widgets such as
buttons or menus whereas instances of the later are objects contained
in a canvas\footnote{The canvas widget afforded by the Tk library
allows 2D structured drawing.} such as lines, ovals or
rectangles. Both kind of Tk objects are directly implemented as
{\stklos} classes in a one-to-one relationship. A partial view of the
{\stklos} hierarchy is shown in Fig.~1. Here are some important
%\centerline{\epsfig{file= hierarchy.eps, clip=, width=13cm,height=13cm}}
\centerline{\psfig{file= hierarchy.eps,width=12.0cm}}
\caption[]{\em A partial view of the {\stklos} hierarchy}
\item In Tk, interface widgets ({\em e.g.} buttons) are
first class objects, but canvas items ({\em e.g.} rectangles) can be
accessed only through their containing canvas. Thus, actions on widgets or
canvas items must be done in different ways. Accessing a canvas item
option requires two references: one to the canvas which contains it and one
to its identification (a number) in this canvas. In order to make canvas
items first class objects, the class {\tt <Tk-canvas-item>} defines the
extra slot {\tt Cid} which contains the Tk identification number associated
to the item.
\item The hierarchical view of Tk widgets permits a better
apprehension of the Tk toolkit, even if there is no notion of inheritance
in standard Tk. According to Fig.~1, a {\em button} can be seen as a
reactive {\em label}. As a consequence, the methods in charge of the look
of a label or button text (font, foreground color, \ldots) can be gathered
in the {\tt <Label>} class. Thus, the {\tt <Button>} class has only to
manage the operations which are specific to a reactive text, such as the
associated command to invoke when the mouse button is depressed over it.
\item Simple and composite widgets share a common
ancestor ({\tt <Tk-widget>}). Consequently, composites widgets, which
are written in Scheme, are controlled exactly in the same way as C
built-in Tk widgets. This kind of widgets is discussed in~\cite{Gallesio94-1}.
\subsection{Accessing Tk Widgets Options}
Each Tk toolkit widget accepts a specific set of options which enables its
aspect customization such as its color, font, text or relief. Options may
be specified either on the command line when the widget is created or with
the {\tt configure} operation which is applicable to all Tk widgets. In
{\stklos}, each option of a Tk widget is seen as an object slot, and
getting or setting the configuration of a Tk option is equivalent to read
or write an object slot. The following example shows a possible {\stklos}
definition of a Tk button.
(define-class <Button> (<Label>)
((command :accessor command :init-keyword :command
:allocation :tk-virtual))
:metaclass <Tk-metaclass>)
This new class inherits from {\tt <Label>} and owns an extra slot
called {\tt command}. The allocation of this slot is qualified with
{\tt :tk-virtual}. \label{tk-virtual}Tk-virtual slots are special
purpose slots: they can be used as normal slots but they are not
allocated in the Scheme world ({\em i.e.} their value is stored in one
of the structures manipulated by the Tk library instead of in a Scheme
object). Consequently, reading or writing such a slot is done in a
particular way: access to Tk-virtual slot uses in turn the standard Tk
{\tt configure} operation as in \ref{configure}. Tk-virtual widgets
slots are a special kind of virtual slots which are managed by the
meta class {\tt <Tk-metaclass>}. Defining a class using this meta
class allows the modification of a slot accessors at the lowest
level. Therefore, the value of a virtual slot always reflect the
actual value of the associated Tk option (remember that no space is
reserved for this slot in the Scheme core and that accesses are
directly done within the Tk data structures). The specification of the
meta class of the {\tt <Button>} class in given with the {\tt
:metaclass} option\footnote{In fact, this meta class citation can be
omitted since {\tt <Label>} (or one of its ancestor) has probably
already specified it. In this case the system will automatically
choose the most specific meta class.}. It is important to note that
the construction of the slot accessors is made at class creation so
that no particular computation is necessary when accessing such a
slot. Thus, customizing a widget by using a slot access at the
{\stklos} level is {\bf as efficient as} using a standard Tk option
configuration at the {\stk} base level.
The previous definition of {\tt <Button>} is not sufficient for a
complete integration of the Tk button widget in a {\stklos} class. Indeed,
the MOP ensures that {\tt Tk-constructor}\label{Tk-constructor} is called
when creating a new {\tt <Tk-widget>} (and {\tt <Button>} is an indirect
instance of {\tt <Tk-widget>} as shown in Fig.~1). This function must
determine the Tk library function (a {\em Tk-command}) which has to be
called to create the new widget. The following method for {\tt
Tk-constructor} suffices to do this job:
(define-method Tk-constructor ((b <Button>))
The previous {\tt <Button>} class and {\tt Tk-constructor} method
definitions are the two only things necessary for defining a new {\stklos}
widget. This point is particularly important since it permits to minimize
the integration cost of new Tk widgets and, consequently, to {\em follow}
future Tk releases with minimal coding.
The following variable definition shows how we can use the above
{\tt <Button>} class:
(define b (make <Button> :font "fixed"
:command (lambda () (display "Hello\n"))))
This expression assigns to the symbol {\tt b} a new instance of
{\tt <Button>}. Changing the font or the command associated to
this object could be done by using either the {\tt slot-set}! or the
generalized {\tt set}! primitives as shown in~\ref{set!}.
\subsection{Comparison of {\stklos} and Standard Tk}
Some of the advantages of {\stklos}, approach over standard Tk have
already been discussed before. In this section, we go on further this
\subsubsection{Low Level Detail Hiding}
One of the most important benefits when embodying Tk widgets in {\stklos}
is that most of the Tk idiosyncrasies are hidden to the user. As a positive
consequence, this improves greatly the level we can program GUI. A
major improvement concerning this point is that we do not need anymore to
take care of the Tk widget naming conventions. The fact that Tk imposes
that the name of a widget must reflect the hierarchy to which it belongs,
and the lack of relative naming conventions are very severe constraints
when designing a GUI. These points make difficult, in standard Tk, the
definition of reusable interface components and usage of long pathnames
(which are current in non toy applications) is very awkward to
manage. Furthermore, these conventions lead to change large pieces of code
as soon as a modification is done in the widget hierarchy. In this sense,
Tk naming conventions do not fit well with GUI programming since the design
of an interface brings aesthetic problems which often conduct to develop it
on a trial and error basis.
In {\stklos}, Tk naming convention are completely hidden and the only
thing the user needs to know when creating a new object is the widget
which contains it. This object is called its parent. An example of
nested widgets creation is shown below:
(define f (make <Frame>))
(define b1 (make <Button> :text "B1" :parent f))
(define b2 (make <Button> :text "B2" :parent f))
The buttons {\tt b1} and {\tt b2} created here specify that their parent
is the frame {\tt f}. Since this frame does not specify a particular
parent, it is supposed to be a direct descendant of the root
window. Note that only the definition of {\tt f} should be changed if
we decide that {\tt f} should no more be a top-level frame. A
modification in the hierarchy of this widget is automatically
propagated to all the widgets belonging to this hierarchy. {\stklos}
also extends this parent notion to take into account canvas items
(rectangles, lines, ovals,~\ldots): a canvas item is considered to be
a descendant of the canvas which contains it. This vision of the
canvas items allows the {\stklos} user to manipulate canvas items as
first class objects. For instance,
(define c (make <Canvas>))
(define r (make <Rectangle> :parent c :coords '(0 0 50 50)))
defines a rectangle called {\tt r} in the {\tt c} canvas. As said
before, accessing this rectangle implies the use of two references in
standard Tk: the canvas which contains it, and its identification
number in this canvas. In {\stklos}, both informations are contained in the object
which represent the rectangle. For instance, after executing the expression,
(bind r "<Enter>" (lambda (x y)
(format #t "Mouse enters in ~A ~A~%" x y)))
a message is displayed, each times the mouse enters the {\tt r}
rectangle. It is important to note here that we would use {\em
exactly} the same expression to associate such a binding to a simple
widget such as a button or a label, whereas it needs two different
syntactic forms in Tcl/Tk, since the procedures which access
a canvas item or an interface widget are different.
\subsubsection{Uniform Access to the Toolkit}
Usage of generic functions is also a significant improvement over the
Tk basic level programming since it allows an homogeneous access to
Tk commands. Suppose that we want to give access to
the value of a {\tt scale} or an {\tt entry} widget with the generic
function {\tt value}. This can easily be done by the following method
in {\stklos}:
(define-method value ((obj <Tk-simple-widget>))
((Id obj) 'get))
In this case, one method is sufficient to implement the getter
function since the Tk sub-option for reading the value of a scale or
an entry is the same. Writing the setter function for those widgets is
a little bit more complicated since the way of changing a scale value
is different from the way of changing an entry value in Tk:
(define-method (setter value) ((obj <Scale>) v)
((Id obj) 'set v))
(define-method (setter value) ((obj <Entry>) v)
((Id obj) 'delete 0 'end)
((Id obj) 'insert 0 v))
Using the same generic function (with two different methods) permits to
hide to the user these low level details and gives him/her a coherent
access to the toolkit. In the call, {\label{value}}
(set! (value x) 100)
the system chooses the method to apply depending on the class of {\tt
x}. Of course, an error\footnote{more exactly, the system calls the
{\tt no-applicable-method} generic function which, by default, signals
an error, as in CLOS. User can specialize this function to provide
another handler.} will be signaled if {\tt x} is not an
entry or a scale. Note that this notion of widget value could also be
easily implemented with a virtual slot (see~\ref{virtual-slot}) even
if {\tt :value} does not exist as a Tk option {\it per~se}. This
approach, which is the one chosen in the current released library,
allows introspecting functions to manage the value of a widget as
a standard slot. In particular, this library offers a small interface
builder which heavily use introspection to automatically build
specialized widget editors. Defining {\tt value} as a virtual slots
for most widgets allows the user to tune it in the same fashion as the
font or the background Tk options. Designing an interface builder
using only standard Tk constructs would have been a lot more
The previous examples show that programming with {\stklos} brings the power
of a full featured object language in the area of GUI
construction. However, this fine integration of the Tk toolkit could not
have been done without the underlying Meta Object Protocol of
{\stklos}. This protocol is discussed in the following section.
In the previous section, we show through several examples the gain provided
by an object language to use the Tk toolkit. The simplicity of these
examples could make think that the definition of an {\em ad-hoc} object
system for Tk widgets could suffice to have an OO vision of the
toolkit. However, we feel that this approach, which has been widely
used for Tcl, is not the good one. Providing a general purpose Scheme
object system, which can easily be customized for using Tk, seems a
far better approach. Indeed, in the GUI area, applications
programmers often need to be able to use introspection on the objects
they manipulate, or they need to define new ways to access object
slots when composing several widgets. These constraints have led us to
define a MOP based object extension for {\stk}, because it is probably
the cleanest way to achieve the requirements expressed above.
This section presents how to integrate Tk widgets in a hierarchy such
as the one shown in Fig.~1. The discussion is split in two
parts. First, we present the services a MOP must offer for this
integration and then, we show how we can exploit them to build
this hierarchy.
\subsection{The {\stklos} Meta Object Protocol}
{\stklos} meta object protocol implementation is based on
Tiny-Clos~\cite{Tiny-Clos}, a minimal MOP written in Scheme. Current
version of {\stklos} MOP is written in C and in Scheme. Code written in C
correspond to the generic functions calls, which allows to implement them
as efficiently as possible. The rest of the implementation, where time
consumption is less important ({\em e.g.}~computation of class
precedence lists or printing methods), is written in Scheme. This conducts
to an efficient implementation where the overhead of OO programming {\it
vs} ``classical'' programming is as low as possible.
As said before, {\stklos} is a general purpose OO extension, but a great
attention has been carried for the services its MOP must provide in order
to integrate easily and efficiently Tk widgets in {\stklos} objects. The
{\stklos} protocol must at least offer following capabilities:
\item a way to intervene in the initialization of a {\stklos} widget.
The first task which must be done at this stage consists to generate a
name (using the Tk conventions) for the widget which will implement
the new instance. Then, the instance creation arguments list must be
filtered to distinguish the user arguments which concern only
{\stklos} ({\em e.g.}~the {\tt parent} slot discussed in
\ref{parent-slot}) from those which correspond to Tk
options. This distinction among parameters is necessary because the Tk
library raises an error when it encounters a parameter it does not know
how to manage.
\item the possibility to define slots with special behaviour and
allocation schemes. In effect, beyond virtual slots already discussed
in \ref{virtual-slot}, the protocol should allow to map the Tk widget
options as slots of a {\stklos} object. Note that the
way to do this mapping will be different for a simple widget and a
canvas item, since Tk offers two different methods for accessing their
options. Furthermore, the protocol for defining slots must be as
simple as possible to allow application programmers to extend the
library with new kinds of widgets.
The creation of a {\stklos} object is done with the {\tt make} generic
function. As in CLOS, this function first allocates a new instance
(by calling the generic function {\tt allocate-instance})
and then returns this instance initialized. Initialization of the
new instance is performed by the {\tt initialize} generic function.
Class slots are computed when the class is initialized. The {\stklos}
MOP calls the generic function {\tt compute-get-n-set} which, given
the definition of a slot, returns a couple of procedures. These
procedures correspond to the reader and writer functions for the
slot. In case of a virtual slot definition (see~\ref{virtual-slot}),
for instance, {\tt compute-get-n-set} returns a list constituted of
the two evaluated lambda expressions given in the {\tt :slot-ref}
and {\tt :slot-set!} options.
\subsection{Using the MOP to Wrap Tk Widgets}
We present here only the salient points which are necessary to the
integration of Tk widgets and simple canvas items in an object
world. The code exposed here is simpler than the one which is used in
the current distribution of {\stk}, but principles are the same. A
complete listing of the source code of this simplified implementation
is given in annex.
\subsubsection{Managing Tk Options as Object Slots.}
When a new class is created, the generic function {\tt
compute-get-n-set} is called for each slot this class defines. This
function takes two parameters: the class which is being created and
the slot definition (a list). This allows us to define a meta class
which takes into account a special kind of slots: {\em tk-virtual}
allocated slots. The meta class in charge of these slots is called
{\tt <Tk-metaclass>}. This class is defined as:
(define-class <Tk-metaclass> (<class>)
((valid-options :accessor Tk-valid-option)))
The slot {\tt valid-options} contains the list of options a Tk widget
recognizes. This slot is initialized when a new widget class is defined
(its value is set to the list of the slots whose allocation is {\em
Tk-virtual}). Usage of {\tt valid-options} will be discussed later.
Tk-virtual slots have been presented in~\ref{tk-virtual}. Reading and
writing this kind of slot implies the use of the {\tt configure} sub-option
which is always available for Tk widgets. In standard Tk, reading the value
of a widget option, such as {\tt width}, for a given widget {\tt w} must
be done with
(list-ref (w 'configure :width) 4)
Setting the width of this widget to the value {\tt val} is a little
bit simpler and can be expressed as:
(w 'configure :width val)
Consider now a canvas item whose enclosing canvas and identification number
are respectively {\tt c} and {\tt id}; reading and writing the value
of its {\tt width} option can be done with
(list-ref (c 'itemconfigure id :width) 4)
(c 'itemconfigure id :width val)
We said in previous subsection that the generic function {\tt
compute-get-n-set} has in charge slot allocation. Given the Tk conventions
exposed before, it is easy to define a {\tt <Tk-metaclass>} specialized
method for this function. This method must return a list whose first
element is a closure for reading the slot, and whose second element is a
closure for its writing:
(define-method compute-get-n-set ((class <Tk-Metaclass>) slot)
(if (eqv? (get-slot-allocation slot) :tk-virtual)
{\em ;; this is a Tk-virtual slot}
(let ((opt (make-keyword (car slot))))
(list (lambda (obj) (list-ref ((Id obj) 'configure opt) 4))
(lambda (obj val) ((Id obj) 'configure opt val))))
{\em ;; call super compute-get-n-set}
This method first tests the allocation type of the slot with {\tt
get-slot-allocation}. If the slot is a Tk-virtual one, this method
returns the reader and writer closures in a list. Otherwise, this
method calls {\tt next-method}, that is to say the {\tt
compute-get-n-set} method defined over the super class of {\tt
Since Tk accesses canvas items options in a different way than simple
widgets ones, {\tt <Tk-metaclass>} cannot be used for reading and
writing their slots. A meta class for canvas items can be simply
defined as
(define-class <Tk-item-metaclass> (<Tk-Metaclass>)
Given this meta class and the Tk conventions shown before, it is simple to
define a {\tt compute-get-n-set} method specialized for canvas items:
(define-method compute-get-n-set ((class <Tk-item-metaclass>) slot)
(if (eqv? (get-slot-allocation slot) :tk-virtual)
(let ((opt (make-keyword (car slot))))
(list (lambda (obj)
(list-ref ((Id obj) 'itemconfigure (Cid obj) opt) 4))
(lambda (obj val)
((Id obj) 'itemconfigure (Cid obj) opt val))))
Two points are important to note here:
\item Methods of the generic function {\tt compute-get-n-set} are
very dependent of the procedure Tk proposes for accessing widget
options. However, it must be noted that only the two returned lambda
expressions should be re-written if the author of the Tk toolkit
decides to change current conventions. We can even say that the MOP
permits to write programs which are less dependent of the Tk toolkit
than Tcl/Tk programs, since dependences can be isolated in a few
methods instead of being spread all over the code.
\item The protocol for accessing the slots of new kind of widgets is
easily customizable. The {\stklos} library uses it for the Tk text tags (a
tag is an annotation which allows to associate a script to a portion of the
string associated to a Tk {\tt text} widget). Defining a meta class for
text tags and a specialized {\tt compute-get-n-set} method suffice to see
them as first class objects whose state is stored in the slots of a
{\stklos} instance. Similarly, the {\stklos} library defines a meta class
for managing composite widgets where a slot access for a compound widget
can be propagated to some of its composing widgets.
\subsubsection{Widget Initialization.}
\label{widget-init}When a new object is initialized, the MOP ensures that the
{\tt initialize} generic function is called. This method must filter
the arguments given to {\tt make} in order to pass only
valid options to Tk, and to take into account slots which deal only with
{\stklos} (such as {\tt parent}, for example). This method is given below:
(define-method initialize ((self <Tk-simple-widget>) initargs)
{\em ;; Use split-options on initargs to separate STklos slots from Tk}
{\em ;; ones. Set parent to the root window if not specified in initargs}
(let* ((options (split-options (Tk-valid-options (class-of self))
(parent (get-keyword :parent (cdr options) *root*)))
{\em ;; Call the Tk command which creates the widget}
(set! (Id self) (apply (tk-constructor self)
(make-tk-name parent)
(car options)))
{\em ;; Initialize other slots (i.e. non Tk-virtual ones)}
(next-method self (cdr options))))
The list of valid Tk options is found in the slot {\tt valid-options} of the
class of the widget which must be initialized. Given this list,
options are separated in two lists with the {\tt split-options} helper
function (this function returns a list whose first element is the set
of Tk options and whose rest contains the other arguments). Given for
instance the call
(define b (make <Button> :parent p :text "Hi" :counter 12))
this method will call the generic function {\tt Tk-constructor} to
find the {\em Tk-command} which implements the widget at the STk basic
level. Then, it calls this command with a
generated name and the list {\tt (:text "Hi")}. As said before, the
value returned by the Tk widget constructor must be stored in the {\tt
Id} slot of the new instance. From this point, all {\em tk-virtual}
slots are initialized and the call to {\tt next-method} at the end of
this method ensures the initializations of other slots with the list
{\tt (:parent p :counter 12)}. So, slots of user defined classes which
inherit from standard {\stklos} classes are properly initialized. The
{\tt initialize} method for canvas items follows the same principles
and is not developed here. Interested readers can find it in the
In order to compare the performances of STk against Tcl ones, we will
compare the {\stk} basic layer with Tcl/Tk first and then we will
compare {\stk} and {\stklos}.
Tcl is by nature an interpreted language and some of its aspects make
difficult the writing of a compiler for this language. Furthermore, the
fact that Tcl is a string language implies that the values manipulated by
a program must always be converted to strings, which is time consuming. This
explains the poor performances of the current Tcl interpreter. Some
compilers for this language have been announced but, to our knowledge, none
has been achieved at this date.
{\stk} current implementation also relies on an interpreter. However, the
semantic of the Scheme programming language has been designed to allow
simple and efficient interpreters or compilers implementations. {\stk}
interpreter is hence small an offers good performances. In particular, it
runs 4 to 7 times faster than Tcl interpreter on general purpose
computations. When using the Tk toolkit, Tcl takes advantage of the way
arguments are passed to the functions of the library. In effect, the Tk
toolkit uses the C language \mbox{\tt argc/argv} classical convention for
arguments passing. Given these conventions, the {\stk} interpreter must
convert to strings the parameters given to {\em Tk commands}, whereas this
conversion is unnecessary in Tcl since everything is kept as strings in the
interpreter. However, the penalty induced by these conversions is generally
negligible facing the overall computation time of a program, and interfaces
written with {\stk} tend to be faster than Tcl ones.
Let us consider now the performances of {\stklos} comparing to {\stk}. In
{\stklos}, as in CLOS, the generic function call mechanism costs a
lot. Because this mechanism can be the bottleneck of a MOP based
architecture, it has been implemented in C to be as fast as
possible. However, the current implementation is relatively direct and
don't use yet the optimizations which can generally be applied on generic
function calls. This explains the relative poor performances of generic
function calls compared to closure invocation. Actually, a generic function
call is 6 times slower than a simple Scheme function call. Consequently, an
interface written in {\stklos} is far much slower than an application
resorting only on simple {\stk} constructs. The generic function overhead
can even makes {\stklos} programs, under certain circumstances, a little
bit slower than Tcl programs. However, applying {\em memoization}
optimization techniques such as the one presented in \cite{Kickzales:PCL}
will decrease the ratio between generic and non generic functions to
provide performances close to the basic layer.
We have shown in this paper the {\stklos} object system and how it can be
tailored to provide an easy access to a standard graphical toolkit. One of
the major benefits of this system is that it allows a neat {\em
reification} of a class-less toolkit. This point is important since it is
generally admitted that an object vision of widgets greatly improves the
level of GUI programming.
{\stklos} provides a real programming language for the Tk toolkit.
Furthermore, the underlying meta object protocol of {\stklos} provides a
pleasant way to access the toolkit options for each widget and it allows us
to hide most of the idiosyncrasies of this toolkit in a clean way. It makes
easier the developement of large Graphical User Interfaces with Tk, and
extend hence the interest of this toolkit.
{\stklos} is distributed with the {\stk} package and runs on a wide variety
of architectures and systems. The last version of this package is available
at the following address: \mbox{\tt}.
\section*{Annex: A Minimal Meta Object Protocol for Tk}
Hereafter is a minimal implementation of the Meta Object Protocol described
in section 3. Three widgets classes ({\tt <Canvas>}, {\tt <Label>} and {\tt
<Button>}) and two canvas items ({\tt <Line>} and {\tt <Rectangle>}) are
defined using this MOP. The protocol exposed here has been simplified to
fit in the limited size of this paper and defined widgets recognize only a
small subset of the Tk options. However, in spite of this size, this MOP is
completely operational.
% LocalWords: Tk coords STk STklos Erick Gallesio Universit de GUI ButtonPress
% LocalWords: reification init accessor ref resp obj obj obj val GC Cid eps tk
% LocalWords: metaclass metaclass aesthetic se vs itemconfigure eqv initargs
% LocalWords: cdr argc argv memoization gz callback