Example
\zline{\fboxwish}
\hspace*{2.5cm}
\epsfbox{hello.eps}
Scripts
-- hello world
button .b -text "Hello, world" -command {
puts stdout "hello world"
}
pack .b
slide: A Wish example
The wish program is an interpreter for executing
Tcl/Tk scripts.
As an example of a wish script, look
at the hello world program in slide [tcl-example].
The hello world script defines a button that
displays Hello, world, and prints hello world
to standard output when it is
activated by pressing the left mouse button.
The language used to write this script
is simply Tcl with the commands defined by Tk,
such as the button command (needed to create
a button) and the pack command (that is used to map
the button to the screen).
The wish program actually provides an example
of a simple application based on Tcl/Tk.
It may easily be extended to include, for example,
3D-graphics by linking the appropriate
C libraries and defining the functions making
this functionality available as (new) Tcl commands.
To define Tcl commands in C,
the programmer has to define a command
function
and declare the function
to be a command in Tcl by
invoking the {\em Tcl_CreateCommand}
function.
Creating a command is done with reference
to an interpreter, which accounts for the
first argument of {\em Tcl_CreateCommand}.
The name of the command, as may be used
in a Tcl script must be given as a second argument,
and the C/C++ function defining the command as a third argument.
Finally, when declaring a command, the address
of a structure containing client data
may be stored, which may
be (the address of) the root window, for example.
When the function is invoked
as the result of executing the Tcl command,
the client data stored at
declaration time is passed as the first
argument to the function.
Since the type ClientData is actually defined
to be , the function must first
cast the client data argument to an appropriate
type.
Evidently, casting is error-prone.
Another problem with command functions
as used in the Tcl C API is that
permanent data are possible only
in the form of client data,
global variables or static local
variables.
Both client data and global variables
are unsafe by being too visible
and static local data are simply inelegant.
Program structure
\nop{
The hush library is intended
to provide a convenient way
to program window-based applications in C++.
As a first example of using hush,
we will look at a simple program
written in C++ that uses a graphical
interface defined by a Tcl/Tk script.
}
\c{
The hush library is intended to
provide a convenient way to program window-based applications in C++.
Basically, there are two considerations
that may lead you to employ
the hush library.
When you are familiar with Tcl/Tk and you need
to combine Tcl scripts with C++ code,
you may use handler classes to
do so in a relatively type-secure way.
On the other hand, when you want to program
graphical user interfaces in C++,
you may employ the hush widget
classes.
In the latter case, you may
choose to remain ignorant of
the underlying Tcl/Tk implementation
or exploit the Tcl script facility
to the extent you wish.
\nop{
Whether you choose to use Tcl scripts
or not, the structure of the program
is to a large extent the same.
}
}
\nop{
As an illustration of the structure of a program
using hush,
we will look at a simple program written in C++
that uses a graphical interface defined
by a Tcl/Tk script.
After discussing the example,
we will look at
a brief overview of the classes
that constitute the hush library.
A more detailed description will
be given of the kit class,
that encapsulates the embedded Tcl interpreter,
and the session class, that shields
of the details of the window environment.
}
\c{
\nop{
The example given in the previous section
showed what kind of components are
typically used when developing a program
with the hush library.
However, instead of employing a Tcl script,
the window interface may also be developed entirely
by employing hush C++ widgets.
}
In this section, a brief overview
will be given of the classes offered by
the hush library.
Further, it will be shown how to construct
the hush interpreter referred
to in the introduction.
In addition, we will take a closer look at
the classes kit and session,
which are needed to communicate with the embedded
Tcl interpreter and to initialize the main event loop.
}
\c{
The hush class library
The hush C++ library consists
of three kinds of classes,
namely (a) the widget classes,
which mimic the functionality of Tk,
(b) the handler classes,
which are involved in the
handling of events and the binding of C++
code to Tcl commands,
and (c)
the classes kit and session,
which
encapsulate the
embedded interpreter
and the window management system,
}
.so f_ov
\c{
In the widget class hierarchy depicted
in slide [hush-overview] (a), the widget class
represents an abstract widget,
defining the commands that are valid for
each of the descendant concrete
widget classes.
The widget class, however, is not an abstract class
in C++ terms.
It may be used for creating
pointers to widgets defined in Tcl.
In contrast, employing the constructor
of one of the concrete widget classes
results in actually creating a widget.
A more detailed example showing the functionality
offered by the widget classes will be given
in section [user].
A description of the individual widget classes
is included in appendix [hush-man].
}
\c{
The class hierarchy depicted in slide [hush-overview] (b)
depicts the handler class as a subclass
of client.
The reason for this will become clear in section [Binding].
The handler class may also be considered
an abstract class, in the sense that
it is intended to be used as the ancestor of
a user-defined handler class.
The handler class has two
pre-defined descendant classes,
namely the widget class and
the item class.
This implies, indeed, that both
the widget and the item classes
(the latter of which is discussed in section [hush-item])
may also be used as ancestor handler classes.
The reason for this is that any descendant
of a widget or item
class may declare itself to
be its own handler and define the actions
that are invoked in response to
particular events.
This will be illustrated and discussed
in sections [Binding] and [user].
}
\c{
The hush interpreter
In the introduction, hush
was announced as both a C++ library
and as an interpreter extending
the wish interpreter.
The program shown in slide [hush-interpreter]
substantiates this claim,
albeit in a perhaps disappointingly simple way.
}
\slide{hush-interpreter}{The hush interpreter}{
.ds lib/sli-hush.c
}{
.ds lib/hush.c
}
}
\c{
The structure of the program is
characteristic for hush-based applications.
\nop{
similar
to the C++ example of section [hush-example].
}
Part [1] consists merely of including
the {\tt hush.h} header file
and may possibly define additional functionality.
\nop{
Part [2] is empty.
}
Part [2] consists of an application class,
derived from session,
defining how the hush interpreter
deals with command-line
arguments (a and d) and the initialization that
takes place when the main event loop is
started (c).
To understand (a) and (d) it is enough
to know that the hush library
provides a hypertext widget and that
the -x option treats the next
argument as the name of a hypertext file.
In section [hyper], an example will be given that
involves the hypertext widget.
In (b) the {\tt hush.tcl} file is declared to be the initialization
file.
It contains the Tcl code for installing the extensions loaded
in (c).
In (e), a predefined button {\tt .quit} is packed to the root
widget.
Part [3] is needed to initialize the application
and start the main event loop.
}
\c{
The hush interpreter defined by the program
extends the wish interpreter
by loading some extensions to Tcl
\nop{discussed in the introduction}
and by allowing for the display of
a hypertext file.
The interpreter accepts any command-line
argument accepted by the wish
interpreter, in addition to the
-x hypertext option.
}
\nop{
The Tcl interface script given
in slide [int-script],
for example, may be executed using the
hush interpreter.
}
\c{
The {\em kit} class
Hush is meant to provide a parsimonious C++
interface to Tcl/Tk.
Nevertheless, as with many a toolkit, some
kind of API shock seems to be unavoidable.
This is especially true for
the widget class (covered in section [hush-widget])
and the class kit
defining the C++ interface with the embedded Tcl interpreter.
The functionality of kit can only be completely
understood after reading this section.
However, since an instance of kit is used in almost
any other object (class), it is presented here first.
See slide [class-kit].
The reader will undoubtly gradually learn the
functionality of kit by studying the examples.
}
\c{
To understand why a kit class is needed,
recall that each hush program contains an embedded
Tcl interpreter.
The kit class encapsulates this interpreter
and provides a collection of member functions
to interact with the embedded interpreter.
}
\nop{
The kit class is derived from the class hcl which
provides access to the lower level details of (an extended version of)
Tcl. \nop{Consult the reference manual.}
}
\c{
The first group of functions (eval, result,
evaluate and source) may be used to execute
commands in the Tcl scripting language directly.
A Tcl command is simply a string conforming to
certain syntactic requirements.
The function eval evaluates a Tcl command.
The function may be used to fetch
the result of the last Tcl command.
In contrast, the function may be used
to set the result of a Tcl command, when this
command is defined in C++ (as may be done with ).
The function evaluate provides a shorthand for
combining eval and .
The function source may be used to read in
a file containing a Tcl script.
}
\c{
Also, we have the function
that may be used to associate a Tcl command with a
handler object.
In section [action],
alternative ways of defining an action are discussed.
}
\nop{
The function after may be used to have a
Tcl command evaluated after n milliseconds.
(It makes use of X timer callbacks).
The function send may be used to evaluate
a command in a different application.
An application may be given a name when
creating the session object.
The function selection delivers the current X selection.
The function update may be used to process any
pending events.
These functions are only for experienced users.
See section [tcl].
}
\c{
The next group of functions is related to widgets
and events that may occur to widgets.
The event function delivers the latest event.
It may only be used in a command that is bound
to some particular event.
See section [hush-events].
When other events occur before accessing the event object,
the information it contains may be obsolete.
}
\slide{class-kit}{The kit class}{
interface kit { \fbox{kit}
int eval(char* cmd);
char* result();
void result(char* s);
char* evaluate(char* cmd);
int source(char* f);
action& action(char* name, handler* h);
class event event();
widget* root();
widget* pack(widget* w, char* opts = "");
widget* pack(char* wp, char* opts = "";
char* selection(char* opts="");
void after(int msecs, char* cmd);
void after(int n, handler* h);
void update(char* opts="");
char* send(char* it, char* cmd);
void trace(int level = 1);
void notrace();
void quit();
};
}{
interface kit { \fbox{kit}
int eval(char* cmd); to evaluate script command
char* result(); to fetch the result of eval
void result(char* s); to set the result of eval
char* evaluate(char* cmd) combines eval and result
int source(char* f); to load a script from file
action& action(char* name, handler* h);
class event event(); returns the last event
widget* root(); returns toplevel (root) widget
widget* pack(widget* w, char* options = "");
widget* pack(char* wp, char* options = "";
char* selection(char* options=""); X environment
void after(int msecs, char* cmd);
void after(int n, handler* h);
void update(char* options="");
char* send(char* it, char* cmd);
void trace(int level = 1);
void notrace();
void quit() to terminate the session
};
}
}
\c{
The function root gives access to the toplevel root
widget associated with that particular
instance of the kit.
The function pack may be used to append
widgets to the root widget, in order to map them to the screen.
Widgets may be identified either by a pointer to
a widget object or by their path name,
which is a string.
See section [widget].
}
\c{
Next, we have a group of functions related to the
X environment.
The function selection delivers
the current X selection.
The function after may be used to set a timer
callback for a handler.
Setting a timer callback means that the
handler object will be invoked after
the number of milliseconds given as the first
argument to after.
The function update may be used to ensure
that all pending events are processed.
For example, when moving items on a canvas,
an update may be needed to make the changes visible.
Also, we have a send function that may be used
to communicate with other Tcl/Tk
applications.
The first argument of send must be the name of
an application, which may be set when creating
a session.
}
\nop{
The function action may be used to associate a Tcl command to
a command function or handler written in C++.
See section [action] for details.
}
\nop{
A kit object, which may be only created by session(),
provides an interface to the tcl interpreter
and the global properties of the system.
The function eval() may be used to execute
commands in tcl and to retrieve the result.
The function event delivers the latest event.
When other events occur before accessing a particular
event object, the information gotten will be obsolete.
}
\c{
Further, we have
the functions and , which may be used to turn
on, respectively off, tracing.
The level indicates at what detail information will
be given. Trace level zero is equivalent to notrace().
Finally, the function quit may be used to terminate
the main event loop.
}
\c{
The {\em session} class
Each program written with hush
may contain only one embedded
interpreter.
To initialize the kit object wrapping
the interpreter and to start the main event loop,
an instance of the class session
must be created.
See slide [class-session].
The preferred way of doing this is by defining
a descendant class of the session class,
redefining the virtual function
to specify what needs
to be done before starting the main loop.
In addition, the constructor of the newly
defined class may be used to check
command line arguments and to initialize
application specific data,
as illustrated in slide [hush-interpreter].
}
\slide{class-session}{The session class}{
interface session { \fbox{session}
session(int argc, char** argv, char* name = 0);
void init(char* fname);
virtual void prelude(kit* tk, int argc, char* argv[] );
virtual void main(kit* tk, int argc, char* argv[] );
int run( );
};
}
\c{
When creating a session object, the name of the application
may be given as the last parameter.
Under this name, the application is known to other
Tk applications, which may communicate with each other
by means of the send command.
The function init may be used to specify a different
initialization script.
This script must include the default hush
initialization script, which is an adapted version
of the original wish initialization script.
}
\c{
Apart from the function main,
a function prelude may also be defined.
When the program is used as an interpreter
(by giving {\tt -f file} as command line arguments)
only the prelude function will be executed,
otherwise prelude will be executed before
main.
In interpreter mode, the main function will
also be executed when the script contains
the command go-back.
Finally, the function run must be called
to actually initialize the program and
start the main loop.
}
Binding actions to events
Handler objects may be bound to Tcl commands or events.
Events are generated by the (X) window environment
in response to actions of the user.
These actions include pressing a mouse button,
releasing a mouse button, moving the mouse, etc.
Instead of explicitly dealing with all incoming
events,
applications delegate control to the environment by
associating a callback function
with each event that is relevant for a particular widget.
This mechanism frees the programmer from the
responsibility of deciding to which widget the event belongs
and what action to take.
Nevertheless, from the perspective of program
design, the proper organization of
the callback functions is not a trivial
matter.
Common practice is to write only a limited number of
callback functions and perform explicit
dispatching according to the type of event.
An object-oriented approach may be advantageous
as a means to organize a collection of
callback functions as member functions of
a single class.
One way of doing this is to define
an abstract event handler class which
provides a virtual member function for
each of the most commonly occurring events.
In effect, such a handler class hides
the dispatching according to the type
of the event.
A concrete handler class may then
be defined simply by overriding
the member functions corresponding to
the events of interest.
Before studying the
abstract handler class in more detail,
we will briefly look at the definition
of the event class.
Events
Events always belong to a particular widget.
To which widget events are actually directed
depends upon
whether the programmer has defined a binding for the
event type.
When such a binding exists for a widget
and the (toolkit) environment decides that
the event belongs to that widget, then the callback
associated with that event is executed.
Information concerning the event may be retrieved
by asking the kit for the latest event.
\slide{class-event}{The event class}{
interface event { \fbox{event}
int type(); \c{ X event type }
char* name(); \c{type as string}
int x();
int y();
...
void* rawevent(); \c{delivers} raw X event
};
}{
interface event { \fbox{event}
int type(); \c{// X event type }
char* name(); \c{// type as string}
int x();
int y();
int button(int i = 0); \c{// ButtonPress}
int buttonup(int i = 0); \c{// ButtonRelease}
int motion(); \c{// MotionNotify}
int keyevent(); \c{// KeyPress or KeyRelease}
int buttonevent(int i = 0); \c{// ButtonPress or Release}
int keycode();
void trace(); \c{// prints event information}
void* rawevent(); // \c{delivers} raw X event
};
}
}
Event objects represent the events generated by
the X-window system.
Each event has a type.
The type of the event can be inspected with which
returns an integer value or which returns
a string representation of the type.
For some of the common event types, such as ButtonPress,
ButtonRelease and MotionNotify, member functions
are provided to facilitate testing.
If an integer argument (1, 2 or 3) is given to button,
buttonup or buttonevent, it is checked whether
the event has occurred for the corresponding button.
The functions x and y deliver the widget
coordinates of the event, if appropriate.
Calling trace for the event results in printing
the type and coordinate information for the event.
When setting the level to 2 this
information is automatically printed.
Programmers not satisfied with this interface can
check the type and access the underlying XEvent at their
own risk.
\c{
Handlers
Handler objects provide a type secure
way in which to deal with local data and global
resources needed when responding to an event.
A handler object may store the information
it needs in instance variables,
when its constructor is called.
See slide [class-handler].
}
\slide{class-handler}{The handler class}{
interface handler : client { \c{\fbox{handler}}
virtual int dispatch(kit* _tk, int _argc, char* _argv[]);
virtual int operator()();
virtual void press( event& ) { }
virtual void release( event& ) { }
virtual void keypress( event& ) { }
virtual void keyrelease( event& ) { }
virtual void motion( event& ) { }
virtual void enter( event& ) { }
virtual void leave( event& ) { }
virtual void other( event& ) { }
protected:
int argc;
char** argv;
kit* tk;
};
}
\c{
Activating a handler object in response
to an event or a Tcl command occurs
by calling the dispatch function
of the handler.
The system takes care of this,
provided that the user has bound
the handler object to a Tcl command
or event.
}
\slide{dispatching}{The dispatch and function}{
int handler::dispatch(kit* _tk, int _argc, char* _argv[]) {
tk = _tk; argc = _argc; argv = _argv;
return this->operator()();
}
int handler::operator()() { \c{\fbox{}}
event e = tk->event();
if (e.type()==ButtonPress) press(e);
else if (e.type()==ButtonRelease) release(e);
else if (e.type()==KeyPress) keypress(e);
else if (e.type()==KeyRelease) keyrelease(e);
else if (e.type()==MotionNotify) motion(e);
else if (e.type()==EnterNotify) enter(e);
else if (e.type()==LeaveNotify) leave(e);
else other(e);
return OK;
}
}{
int handler::dispatch(kit* _tk, int _argc, char* _argv[]) {
tk = _tk; argc = _argc; argv = _argv;
return this->operator()();
}
int handler::operator()() { \fbox{}
event e = tk->event();
if ( e.type() == ButtonPress ) press(e);
else if ( e.type() == ButtonRelease ) release(e);
else if ( e.type() == KeyPress ) keypress(e);
else if ( e.type() == KeyRelease ) keyrelease(e);
else if ( e.type() == MotionNotify ) motion(e);
else if ( e.type() == EnterNotify ) enter(e);
else if ( e.type() == LeaveNotify ) leave(e);
else other(e);
return OK;
}
}
}
\c{
The dispatch function, in its turn,
calls the function,
after storing the kit, argc
and argv arguments in the corresponding
instance variables.
See slide [dispatching].
The function
fetches the latest event from the kit
object and selects one of the predefined
member functions (press, motion, release, etc.)
according to the type of the event.
The original handler class knows only
virtual functions.
Each of these function, including
the dispatch and
function, may be redefined.
The two-step indirection, via the
dispatch and functions,
is introduced to facilitate
derivation by inheritance,
directly from the handler
or from classes that are themselves
derived from the handler class,
such as the widget classes.
}
Actions
Handler objects will be activated only
when a binding has been defined,
by using
or implicitly by employing
or .
Such bindings may also be defined
by or and
.
Implicit binding results in the creation of
an anonymous action.
Actions, as characterized by the class action,
provide the actual means with which to bind C++
code to Tcl commands.
Actions may be defined either trivially
by a Tcl command,
by a C/C++ command function
or by handler objects.
In effect, defining an action by means
of a handler object amounts, implicitly,
to defining a command function with a
privileged client for which, by convention,
the dispatch function is invoked.
}
\slide{class-client}{The client class}{
Client
client
class client { }; empty class
Command
command
typedef command(client*, kit*, int argc, char* argv[]);
}
Data passed to a command function must be of the type
client, which is defined by an empty
class introduced only to please the compiler.
In slide [class-client] the type definition of command is given.
Apart from the parameter, a command
function must also declare a parameter
and an argc and argv parameter, similar to that for main.
The data of a command (and similarly for
the clientdata parameter of a tclcommand)
can be any kind of class.
However, it is to be preferred that such classes
are made a subclass of client.
The client data pointer is declared when creating
an action and passed to the command function when the actual call
is made.
The other parameters (argc and argv) depend
on the actual call.
The use of argc and argv comes from the original
C interface of Tcl.
It proves to be a very flexible way of
communicating data, especially in string-oriented
applications.
interface action { \fbox{action}
action(char* name);
action(char* name, handler* h);
action(char* name, command f, client* data = 0);
action(char* name, tcl_command f, ClientData data = 0);
action( handler* h); \ifsli{}{//} anonymous
action( command f, client* data = 0);
action( tcl_command f, ClientData data = 0);
char* name(); \c{// returns the name of an action}
};
slide: The action class
The class action offers no less than seven
constructors.
The first constructor, which takes a ()
string as a parameter, is merely
for convenience.
It may be used to convert the name of a Tcl script command
into an action.
The following three constructors differ from the last
three constructors only by their first string parameter,
which serves to define the name under which the
action will be known by the Tcl script interpreter.
The last three constructors, in contrast,
create anonymous actions, of which the user, however,
can ask the name by invoking the function name.
The preferred form of creating an action is by giving it
(apart from a name) a handler as a parameter.
As already noted,
handler objects offer a type-secure way of dealing with client
information.
In contrast, the second constructor of this group,
which takes a command function and possibly a pointer
to client data as parameters, may make (type-insecure) conversions
of the client data necessary.
The constructor taking a {\em tcl_command} and ClientData as parameters
is incorporated for compatibility reasons only.
When using any of the last six constructors,
as a side-effect an association is created between
the name of the action and a Tcl command.
If such a Tcl command already exists,
the previous association will be overwritten.
This is also the case if it has been defined
as a Tcl script command.
User interface widgets
\c{
The Tk toolkit offers numerous built-in widgets.
The Tk widgets conform to the look-and-feel
of the OSF/Motif standard.
The hush C++ interface for Tk provides
for each Tk widget a class of the same name
which supports the creation of a widget
and allows the user to access and modify it.
In addition to the standard Tk widgets,
the hush library includes
a number of other widgets, such as a barchart,
hypertext and photo widget (created by other Tk
adepts).
Also, widgets of our own making are offered, such as a filechooser
and MPEG video widget.
}
\fslide{eps-drawtool}{ The drawtool interface}{
\epsfbox{drawtool.eps}
}
In this section we will look at the realization of simple drawing tool.
The example illustrates how to use the
hush library widgets, and
serves to illustrate
in particular how handlers may be attached
to widgets, either by declaration or
by inheritance, and how to construct
compound widgets.
Our approach may be considered object-oriented, in the sense that each component
of the user interface is defined by a class
derived from a widget class.
It must be pointed out beforehand that the main difficulty
in defining compound or mega widgets is not the construction
of the components themselves, but delegating the configuration
and binding instructions to the appropriate components.
Section [new] shows
how a compound widget
defined in C++ may be made to correspond
to a widget command that may be used
in a Tcl script.
Ideally, defining a new widget
includes both the definition
of a C++ class interface and
the definition of the corresponding Tcl
command.
Our drawing tool consists of a tablet,
which is a canvas with scrollbars
to allow for a large size canvas
of which only a part is displayed,
a {\em menu_bar}, having a File
and an Edit menu, and a toolbox,
which is a collection of buttons for
selecting from among the drawing facilities.
In addition, a help facility is offered.
See slide [eps-drawtool].
class application : public session { \c{\fbox{application}}
public:
application(int argc, char* argv[])
: session(argc,argv,"drawtool") {}
void main(kit* tk, int argc, char* argv[]) {
widget* root = tk->root();
frame* f = new frame(root,".frame");
tablet* c = new tablet(f);
toolbox* b = new toolbox(root,c);
menubar* m = new menu_bar(f,c,b);
tk->pack(m)->pack(c);
tk->pack(b,"-side left")->pack(f,"-side right");
}
};
slide: The drawing tool
In slide [draw-tool-x] the application class for drawtool
is depicted.
Before the main event loop is started,
the components of the drawing tool are
created and packed to the root widget.
In addition to the tablet, {\em menu_bar} and toolbox,
a frame widget is created to pack the
menubar and tablet together.
This is needed to ensure that the geometrical
layout of the widget comes out right.
Each of the component widgets is given
a pointer to the root widget.
In addition, a pointer to the tablet
is given to the toolbox and a pointer to
the toolbox is given to the {\em menu_bar}.
The reason for this will become clear
when discussing the toolbox and
{\em menu_bar} in sections [buttons] and [menus],
respectively.
In the example, no attention will be paid to memory management.
\c{
Configuring widgets
Widgets are the elements from which a GUI is made.
They appear as windows on the screen to display
text or graphics and may respond to events
such as motioning the mouse or pressing
a key by calling an action associated with that event.
Usually, the various widgets constituting the user
interface are (hierarchically) related to each other,
such as in the drawtool application which contains
a canvas to display graphic elements,
a button toolbox for selecting the graphic
items and a menubar offering various options
such as saving the drawing in a file.
}
\c{
Widgets in Tk are identified by a path name.
The path name of a widget reflects its possible
subordination to another widget.
See slide [widget-hierarchy].
}
.so f_lay
\c{
Pathnames consist of strings separated by dots.
The first character of a path must be a dot.
The first letter of a path must be lower case.
The format of a path name may be expressed in BNF form as
-
For example `.' is the path name of the root widget,
whereas `.quit' is the path name of a widget
subordinate to the root widget.
A widget subordinate to another widget
must have the path name of that widget
as part of its own path name.
For example, the widget `.f.m' may have a widget `.f.m.h'
as a subordinate widget.
Note that the widget hierarchy induced by the path names
is completely orthogonal to the widget class inheritance
hierarchy depicted in
slide [hush-overview] (a) and
slide [widget-classes].
With respect to the path name hierarchy, when speaking of
ancestors we simply mean superordinate widgets.
}
\c{
Pathnames are treated somewhat more liberally in hush.
For example, widget path names may simply be defined
or extended by a string.
The missing dot is then automatically inserted.
}
\slide{class-widget}{The widget class}{
interface widget : handler { \c{\fbox{widget}}
widget(char* p);
widget(widget& w, char* p);
char* type(); \c{returns type of the widget}
char* path(); \c{returns path of the widget}
int eval(char* cmd); \c{invokes "thepath() cmd"}
char* result(); \c{returns the result of eval}
char* evaluate(char* cmd) \c{combines eval and }
virtual configure(char* cmd); \c{uses '' }
virtual geometry(int xs, int ys); \c{width x height}
widget* pack(char* opts = "" );
widget* pack(widget* w, char* = "" );
bind(char *b, handler* h, char* = "" );
bind(char *b, action& ac, char* = "" );
handler(class handler* h, char* = "" );
handler(action& ac, char* = "" );
...
void destroy(); \c{removes widget from the screen}
void* tkwin(); \c{gives access to Tk window}
widget* self(); \c{for constructing} mega widgets
void redirect(widget* w);
protected:
char* thepath(); \c{delivers the} virtual path
virtual install(action&,char* ="");
};
}{
interface widget : handler { \fbox{widget}
widget(char* p);
widget(widget& w, char* p);
char* type(); \c{// returns type of the widget}
char* path(); \c{// returns path of the widget}
int eval(char* cmd); \c{// invokes "thepath() cmd"}
char* result(); \c{// returns the result of eval}
char* evaluate(char* cmd) \c{// combines eval and }
virtual void configure(char* cmd); \c{// uses '' }
virtual void geometry(int xs, int ys); \c{// width x height}
widget* pack(char* options = "" );
widget* pack(widget* w, char* options = "" );
bind(char *b, handler* h, char* args = "" );
bind(char *b, action& ac, char* args = "" );
handler(class handler* h, char* args = "" );
handler(action& ac, char* args = "" );
void xscroll(scrollbar* s); \c{// to attach scrollbars}
void yscroll(scrollbar* s);
void focus(char* options="");
void grab(char* options="");
void destroy(); \c{// removes widget from the screen}
void* tkwin(); \c{// gives access to Tk window}
widget* self(); \c{// for constructing} mega widgets
void redirect(widget* w);
protected:
char* thepath(); \c{// delivers} the virtual path
virtual install(action&,char* args="");
};
}
}
\c{
Calling the constructor widget as in
widget* w = new widget(".awry");
does not result in creating an actual
widget but only defines a pointer to the widget
with that particular name.
(It is not an abstract class, technically, since it does not
contain any pure virtual functions, see section [abstract-class].)
If a widget with that name exists, it may be treated
as an ordinary widget object, otherwise an error will occur.
The constructor creates
a widget by appending the path name path to the
path name of the argument widget w.
The function path delivers the path name of
a widget object.
Each widget created by Tk actually defines a
Tcl command associated with the
path name of the widget.
In other words, an actual widget may be regarded
as an object which can be asked to evaluate commands.
For example a widget `.b' may be asked to change
its background color by a Tcl command like
.b configure -background blue
The functions eval, result and evaluate
enable the programmer to apply Tcl commands
to the widget directly, as does the configure
command.
The function geometry sets the width and height of
the widget.
}
\c{
Naming widgets in a hierarchical fashion does not imply
that the widgets behave accordingly.
The widget class interface offers two
pack functions.
The function
applies to individual widgets.
As options one may specify, for example,
{\tt -side X}, where {\tt X} is either
top, bottom, left
or right,
to pack the widget to the appropriate side
of the cavity specified by the ancestor widget.
Other options are -fill x or
-fill y, to fill up the space
in the appropriate dimensions,
or {\tt -padx N} or {\tt -pady N}, for
some integer {\tt N},
to surround the widget with some extra space.
Alternatively, the function
may be used, which allows for the same options
but applies packing to the widget parameter.
This function is convenient when packing
widgets in a frame or toplevel widget.
As a remark, the function may only
be used to pack widgets to the root window.
}
\c{
Binding events
Widgets may respond to events.
To associate an event with an action,
an explicit binding must be specified
for that particular widget.
Some widgets provide default bindings.
These may, however, be overruled.
The function bind is used to associate
handlers or actions with events.
The first string parameter of
bind may be used to specify
the event type.
Common event types are,
for example,
{\tt ButtonPress}, {\tt ButtonRelease}
and {\tt Motion}, which are the
default events for canvas widgets.
Also keystrokes may be defined as events,
for example {\tt Return},
which is the default event for
the entry widget.
The function may be
used to associate a handler object
or
action with the default bindings
for the widget.
Concrete widgets may not override the handler
function itself, but must define the protected
virtual function install.
Typically, the install function consists
of calls to bind
for each of the event types that is
relevant to the widget.
For both the bind and handler
functions, the optional args
parameter may be used to specify
the arguments that will be passed
to the handler or action
when it is invoked.
For the button widget, for example,
the default install function supplies
the text of the button as an additional
argument for its handler.
}
\c{
In addition, the widget class offers four
functions that may be used
when defining compound or mega widgets.
The function must by used
to delegate the invocation
of the eval, configure, bind and handler functions
to the widget w.
The function gives access to the widget
to which the commands are redirected.
After invoking redirect,
the function thepath will deliver
the path that is determined by .
In contrast, the function path will still deliver
the path name of the outer widget.
Calling redirect when creating the compound widget
class suffices for most situations.
However, when the default events must be changed or the
declaration
of a handler must take effect for several component widgets,
the virtual function install must be redefined
to handle the delegation explicitly.
How redirect and install actually work will
become clear in the examples.
}
\c{
Handlers
A note on terminology is in place here.
The reader may be a little astonished by
the fact that we have both a handler
class and a handler function,
which is more properly written
as .
The situation may even seem more confusing
when realizing that the widget
class itself is a descendant of the
handler class.
Schematically, we have the situation as depicted
in slide [h-disc].
}
\slide{h-disc}{Handler classes and functions}{
class widget : public handler {
public:
...
void handler(class handler* h) { ... }
...
};
}
\c{
Note that there is no ambiguity here.
A handler object is an object
that may be invoked in response to a Tcl command
or an event.
The handler function declares
a handler object to be responsible
for dealing with the events
that are of interest to the widget.
Note that since a widget is a handler instance,
it may declare itself as a handler for the incoming
events.
}
Buttons
As the first component of the drawing tool,
we will look at the toolbox.
The toolbox is a collection of buttons
packed in a frame. See slide [draw-toolbox].
class toolbutton : public button { \fbox{toolbutton}
public:
toolbutton(widget* w, char* name)
: button(w,name) { (a)
text(name); handler(w,name); pack(); (b)
}
};
class toolbox : public frame { \fbox{toolbox}
public:
toolbox(widget* w, tablet* t)
: c(t), frame(w,"toolbox") { (c)
button* b1 = new toolbutton(this,"move");
button* b2 = new toolbutton(this,"box");
button* b3 = new toolbutton(this,"circle");
button* b4 = new toolbutton(this,"arrow");
}
int operator()() { (d)
c->mode(argv[1]);
return OK;
}
private:
tablet* c;
};
slide: The toolbox
Each button is an instance of
the class toolbutton.
When a toolbutton is created (a),
the actual button is given the name of the button
as its path.
Next, (b) the button is given the name as its text,
the ancestor widget w is declared to be the
handler for the button and the button is packed.
The function text is a member function
of the class button, whereas both
handler and pack are common widget functions.
Note that the parameter name is used as
a path name, as the text to display, and as an argument
for the handler, that will be passed
as a parameter when invoking the handler object.
The toolbox class inherits from the frame widget class,
and creates a frame widget with a path relative to the widget parameter
provided by the constructor (c).
The constructor further creates the four toolbuttons.
The toolbox is both the superordinate widget
and handler for each toolbutton.
When the function of the toolbox is invoked
in response to pressing a button,
the call is delegated to the mode function
of the tablet (d).
The argument given to mode corresponds to
the name of the button pressed.
The definition of the toolbutton and toolbox
illustrates that a widget need not necessarily be its
own handler.
The decision, whether to define a subclass which is made
its own handler or
to install an external handler
depends upon what is considered the most convenient
way in which to access the resources needed.
As a guideline, exploit the regularity of the application.
class menu_bar : public menubar { \fbox{menu_bar}
public:
menu_bar(widget* w, tablet* t, toolbox* b)
: menubar(w,"bar") {
configure("-relief sunken");
menubutton* b1 = new file_menu(this,t);
menubutton* b2 = new edit_menu(this,b);
button* b3 = new help_button(this);
}
};
slide: The {\em menu\_bar}
\c{
Menus
The second component of our drawing tool is the menubar.
The class {\em menu_bar}, depicted in slide [draw-menubar]
is derived from the hush widget menubar.
Its constructor requires an ancestor widget,
a tablet and a toolbox.
The tablet is passed as a parameter to the {\em file_menu},
and the toolbox to the {\em edit_menu}.
In addition, a {\em help_button} is created, which provides
online help in a hypertext format when pressed.
The help facility will be discussed in section [hyper].
}
class file_menu : public menubutton { \fbox{file_menu}
public:
file_menu(widget* w, tablet* t)
: c(t), menubutton(w,"file") { (a)
configure("-relief sunken");
text("File");
pack("-side left");
f = new file_handler(c); (b)
(c)
class menu* m = new class menu(this,"menu");
this->menu(m); declares it for the menubutton
m->handler(this); (d)
m->entry("Open");
m->entry("Save");
m->entry("Quit");
}
int operator()() {
if (!strcmp(argv[1],"Quit")) tk->quit(); (e)
else f->dispatch(tk,argc,argv);
return OK;
}
protected:
tablet* c;
file_handler* f;
};
slide: The {\em file\_menu}
\c{
A menubar consists of menubuttons to which actual menus are attached.
Each menu consists of a number of entries,
which may possibly lead to cascaded menus.
The {\em file_menu} class, depicted in slide [draw-file],
defines a menu, but is derived from menubutton
in order to attach the menu to its menubar
ancestor (a).
Its constructor defines the appearance of
the button and creates a {\em file_handler} (b).
It then defines the actual menu (c).
The menu must explicitly be attached to
the menubutton by invoking the menubar
member function menu.
For creating the menu, the keyword class is needed
to disambiguate between the creation of an instance of the class
menu and the call of the function.
Before defining the various entries of the menu,
the {\em file_menu} instance is declared
as the handler for the menu entries (d).
However, except for the entry Quit, which is handled
by calling the function (e),
the calls are delegated to the previously created {\em file_handler}.
}
\c{
The second button of the {\em menu_bar} is defined by the
{\em edit_menu}.
The {\em edit_menu} requires a toolbox and creates
a menubutton.
It configures the button and
defines a menu containing two entries,
one of which is a cascaded menu.
Both the main menu and the cascaded menu
are given the toolbox as a handler.
This makes sense only because
for our simple application,
the functionality offered by
the toolbox and {\it edit_menu} coincide.
}
class drawmode { \fbox{drawmode}
public: enum { move, box, circle, arrow, lastmode };
};
class tablet : public canvas { \fbox{tablet}
public:
tablet(widget* w, char* options="");
int operator()() { \fbox{}
return handlers [mode]->dispatch(tk,argc,argv);
}
void mode(char* m);
protected:
void init(char* options);
int _mode;
class handler* handlers[drawmode::lastmode];
canvas* c;
};
slide: The {\em tablet}
Defining actions
The most important component of our drawtool
application is defined by the tablet widget class
depicted in slide [draw-tablet].
The various modes supported by the drawing tool are
enumerated in a separate class drawmode.
The tablet class itself inherits from the canvas
widget class.
This has the advantage that it offers the full functionality
of a canvas.
In addition to the constructor and function,
which delegates the incoming event to the appropriate
handler
according to the {\em _mode} variable,
it offers a function mode, which sets the mode of the
canvas as indicated by its string argument,
and a function init that determines the creation
and geometrical layout of the component widgets.
As instance variables, it contains an integer {\em _mode}
variable and an array of handlers that contains
the handlers corresponding to the modes supported.
See section [canvas] for an example of a typical
canvas handler.
Dispatching
Although the tablet must act as a canvas,
the actual tablet widget is nothing but a frame
that contains a canvas widget as one of its components.
See slide [tablet-op].
tablet::tablet(widget* w, char* options) \fbox{tablet}
: canvas(w,"tablet",0) { (a)
widget* top = new frame(path());
init(options);
redirect(c); (b)
handler(this); (c)
handlers[drawmode::move] = new move_handler(this);
handlers[drawmode::box] = new box_handler(this);
handlers[drawmode::circle] = new circle_handler(this);
handlers[drawmode::arrow]=new arrow_handler(this);
_mode = drawmode::move;
}
slide: Installing the handlers
This is reflected in the invocation of the
canvas constructor (a).
By convention, when the options parameter is 0
instead of the empty string, no actual widget
is created but only an abstract widget,
as happens when calling the widget class constructor.
Instead of creating a canvas right away,
the tablet constructor creates a top frame,
initializes the actual component widgets,
and redirects the eval, configure, bind
and handler invocations to the subordinate canvas
widget (b).
It then
declares itself to be its own handler,
which results in declaring itself to be the handler for the
canvas component (c).
Note that reversing the order of calling redirect
and handler would be disastrous.
After that it creates the handlers for the various modes
and sets the initial mode to move.
The function takes care of dispatching calls
to the appropriate handler.
The dispatch function must be called to pass
the tk, argc and argv parameters.
class drawtool : public canvas { \fbox{drawtool}
public:
drawtool() : canvas() { } (I)
drawtool(char* p, char* opts="") : canvas(p,0) {
init(opts);
redirect(c);
}
int operator()(){ (II)
if ( !strcmp( "drawtool" ,*argv) )
create(--argc,++argv);
else if (!strcmp("self",argv[1]) )
tk->result(self()->path());
else self()->eval( flatten(--argc,++argv) );
return OK;
}
protected:
tablet* c;
void init(char* options);
void create(int argc, char* argv[]) { (III)
char* name = *argv;
char* args = flatten(--argc,++argv);
handler* h = new drawtool(name, args );
tk->action(name,h);
}
};
slide: The {\em drawtool} widget command
\c{
Creating new widgets
Having taken care of the basic components
of the drawing tool, that is the toolbox,
{\em menu_bar} and tablet widgets, all that remains
to be done is to define
a suitable {\em file_handler},
appropriate handlers for the
various drawing modes
and a {\em help_handler}.
We will skip the {\em file_handler}, but look at the
latter two issues in
sections [canvas] and
[hyper], respectively.
However, before that it will be shown how we may grant
the drawtool the status of a veritable
Tk widget, by defining a
drawtool handler class
and a corresponding drawtool widget command.
See slide [draw-new].
}
\c{
Defining a widget command involves three steps: (I)
the declaration of the binding between
a command and a handler,
(II) the definition of the
action function, which actually defines a mini-interpreter,
and (III) the definition of the actual
creation of the widget and its declaration
as a Tcl/Tk command.
Step (I) is straightforward.
We need to define an empty handler, which will
be associated with the drawtool command
when starting the application. See slide [new-command] (a).
The functionality offered by the
interpreter defined by the
function in (II) is kept quite simple,
but may easily be extended.
When the first argument of the call is drawtool,
a new drawtool widget is created as specified in (III),
except when the second argument is self.
In that case,
the virtual
path of the widget is returned, which is actually
the path of the tablet's canvas.
It is the responsibility of the writer of the script
that the self command is not addressed to the
empty handler.
If neither of these cases apply,
the function is invoked
for , with the remaining arguments flattened
to a string.
This allows for using the drawtool almost as an ordinary
canvas. See the example hypertext script shown in
section [hyper].
}
\c{
The creation of the actual widget and
declaration of the corresponding Tcl command,
according to the Tk convention,
is somewhat more involved (III).
Recall that each Tk widget is identified by
its path, which simultaneously
defines a command that may be used
to configure the widget or,
as for a canvas,
to draw figures on the screen.
Hence, the function create must
create a new widget and declare the widget
to be the handler of the command
corresponding to its path name.
}
\slide{new-command}{The drawtool application}{
.ds draw/sl-app.s
}{
.ds draw/applic.s
}
}
\c{
The application class depicted in slide [new-command]
will by now look familiar,
except for the function prelude.
In the body of the prelude function,
the Tcl command drawtool is declared,
with an instance of drawtool as its handler (a).
In this way, the drawtool widget
is made available as a command when
the program is used as an interpreter.
However, in the main function
this declaration is overridden (b).
Instead, the actual drawtool widget
is made the handler of the command,
to allow for a script to
address the drawtool by calling
drawtool self.
}
Delegation
You may by now have lost track of
how delegation within a compound
widget takes place.
Perhaps a brief look at the implementation
will clarify this.
Each eval, configure or bind
function call for a widget
results in a command addressed to
the path of the widget.
By redirecting the command to a different path,
the instructions may be delegated to
the appropriate (component) widget.
Delegation occurs, in other words,
by directing the commands to the widget's virtual
path, which is obtained by the protected
function .
In contrast, the function delivers
the path of the widget's outer component.
Indirection takes place by invoking the function
, which relies on an instance variable
{\em _self} that may be set by the redirect
function.
}
\fslide{f-self}{Dereferencing }{
- \hspace*{-2cm}
\begin{mfpic}[25][12]{-2}{10}{0}{10}
\rect{(1,1),(9,9)}
\rect{(3,1.4),(8.8,6)}
\rect{(4.5,1.8),(8.6,4)}
\cdisk{(1.5,2.3),0.2}
\cdisk{(3.5,2.3),0.2}
\cdisk{(5.0,2.3),0.2}
\curvedarrow{(1.7,2.3),(2.3,2.0), (3.0,2.3)}
\curvedarrow{(3.7,2.3),(4.0,2.0),(4.5,2.3)}
\curvedarrow{(5.2,2.3),(5.5,2.0), (5.7,2.3), (5.5,2.6), (5.3,2.3)}
\label[tl]{1.2}{8.5}{drawtool}
\label[tl]{3.2}{5.5}{tablet}
\label[tl]{4.7}{3.5}{canvas}
\end{mfpic}
}{
\begin{mfpic}[25][8]{0}{10}{0}{10}
\rect{(1,1),(9,9)}
\rect{(3,1.4),(8.8,6)}
\rect{(4.5,1.8),(8.6,4)}
\cdisk{(1.5,2.3),0.2}
\cdisk{(3.5,2.3),0.2}
\cdisk{(5.0,2.3),0.2}
\curvedarrow{(1.7,2.3),(2.3,2.0), (3.0,2.3)}
\curvedarrow{(3.7,2.3),(4.0,2.0),(4.5,2.3)}
\curvedarrow{(5.2,2.3),(5.5,2.0), (5.7,2.3), (5.5,2.6), (5.3,2.3)}
\label[tl]{1.2}{8.5}{drawtool}
\label[tl]{3.2}{5.5}{tablet}
\label[tl]{4.7}{3.5}{canvas}
\end{mfpic}
}
}
The implementation of and is
simply:
char* thepath() { return self()->path(); }
widget* self() { return _self?_self->self():this; }
Hence, resolving a compound widget's
primary inner component relies on simple pointer chasing,
which may be applied recursively to an
arbitrary depth at acceptable costs.
Graphics and hypertext
\c{
The Tk toolkit offers powerful facilities
for graphics and (hyper)text
(see Ousterhout, 1993).\index{Ousterhout (1993)}
In this section we will discuss only the canvas
widget offered by Tk.
Instead of looking at the text widget
provided by Tk, we will (briefly)
look at the hypertext widget,
which presents an alternative approach to
defining hyperstructures.
}
\slide{class-item}{The item class}{
interface item { \c{\fbox{item}}
operator int(); \ifsli{}{returns} item index
void configure(char* cmd); \c{uses canvas}
void tag(char* s); sets tag \c{for item}
char* tags(); delivers tags \c{set for the item}
void move(int x, int y);
bind(char *b, handler* h, char* = "" );
bind(char *b, action& ac, char* = "" );
handler(class handler* h, char* = "" );
handler(action& ac, char* = "" );
protected:
virtual install(action&,char* ="");
};
}{
interface item { \fbox{item}
operator int(); \ifsli{}{returns} item index
void configure(char* cmd); \c{uses canvas}
void tag(char* s); sets tag \c{for item}
char* tags(); delivers tags \c{set for the item}
void move(int x, int y);
bind(char *b, handler* h, char* args = "" );
bind(char *b, action& ac, char* args = "" );
handler(class handler* h, char* args = "" );
handler(action& ac, char* args = "" );
protected:
virtual install(action&,char* args="");
};
}
}
\c{
The $item$ class
The canvas widget allows the programmer to
create a number of built-in graphic items.
Items are given a numerical index when created and,
in addition, they may be given a (string) tag.
Tags allow items to be manipulated in a group-wise fashion.
To deal with items in a C++ context, the hush library
contains a class item of which the functionality
is shown in slide [class-item].
}
\c{
Instances of item may not be created directly by
the user, but instead are created by the canvas
widget.
For an item, its index may be obtained by casting
the item to int.
If the index does not identify an existing item,
it will be zero.
Existing items may be moved, in a relative way,
by the move function.
}
\c{
In a similar way as for widgets,
items may be associated with events,
either explicitly by using
, or
implicitly by using .
The default bindings for items are
identical to the default bindings
for the canvas widget, but these may be overridden
by descendant classes.
Similar to the widget class, the item class
is derived from the handler class.
This allows the user to define possibly compound
shapes defining their own handler.
}
The $canvas$ widget
The Tk canvas widget offers a powerful means for
creating structured graphics.
The hush class canvas provides merely
a simplified interface to
the corresponding Tk widget.
}
class move_handler : public handler { \fbox{move_handler}
public:
move_handler( canvas* cv ) : c(cv) { dragging = 0; }
void press( event& e ) {
x = e.x(); y = e.y();
id = c->overlapping(x, y);
if (id) dragging = 1;
}
void motion( event& e ) {
if (dragging) {
id.move( e.x() - x, e.y() - y );
x = e.x(); y = e.y();
}
}
void release( event& ) { dragging = 0; }
protected:
canvas* c; int dragging; item id; int x,y;
};
slide: The {\em move\_handler} class
As an example of the use of a canvas, consider the
definition of the move_handler
class in slide [draw-move].
The move_handler class is derived from the class
handler.
It makes use of the dispatch and function defined for
handler,
but redefines the (virtual) functions
press, motion and release.
When creating an instance of move_handler,
a pointer to the canvas must be given to the constructor.
In addition, the class has data members to record
position coordinates
and whether a particular item is being moved.
Actually, moving an item occurs by pressing the (left)
mouse button on an item and dragging the item along.
When the mouse button is released, moving stops.
To identify the item, the function overlapping
is used.
The movement is determined by the distance
between the last recorded position and the
current position of the cursor.
\fslide{hyper}{Hypertext help}{
\epsfbox{help.eps}
}
In an analogous manner,
a box_handler may be defined.
The box_handler sets dragging
to true when the button is pressed
and creates a rectangle of zero width and height.
Each time the function motion is called,
the item created in the previous round is deleted
and a new rectangle is created by calling
c->rectangle(x,y,e.x(),e.y());
where c is a pointer to the canvas
and x and y the button pointer coordinates
stored when dragging began.
For circles and lines, it suffices to replace the call
to rectangle with a call to the appropriate
figure creation function.
\c{
The $hypertext$ widget
Both the Tk canvas and text widget allow the binding of
actions to particular items
and hence defining dynamically what we may call
hyperstructures.
A different, in a way more static, approach is offered
by the hypertext widget originally developed by
George Howlett. %%george.howlett@att.com.
}
\slide{help-text}{ A hypertext help file }{
Rubber banding: press the left mouse button
and release when the rectangle is of appropriate
size
%%
drawtool $this.draw
$this append $this.draw
$this.draw create rectangle 20 20 80 80
$this.draw create rectangle 10 30 70 90
$this.draw create oval 40 40 90 90
$this append $this.draw
%%
For additional information click on the %%
button $this.goto -text instruction -command end-of-text
$this append $this.goto
%%
button. Press %%
button $this.quit -command { destroy . } -text quit
$this append quit
%% to remove the window.
}{
Rubber banding: press the left mouse button
and release when the rectangle is of appropriate
size
%%
drawtool $this.draw
$this append $this.draw
$this.draw create rectangle 20 20 80 80
$this.draw create rectangle 10 30 70 90
$this.draw create oval 40 40 90 90
$this append $this.draw
%%
For additional information click on the %%
button $this.goto -text instruction -command end-of-text
$this append $this.goto
%%
button. Press %%
button $this.quit -command { destroy . } -text quit -bg pink
$this append quit
%% to remove the window.
}
}
\c{
The hypertext widget may be used to display text
files containing embedded Tcl code.
The Tcl code must be placed between
escapes, that take the form of {\tt %%}
for both the start and end of the code.
A screen shot of a fragment of the online help
for drawtool is given in slide [hyper].
Notice that the online help provides a
replica of the drawtool application, surrounded
by text.
When looking at (again a fragment of) the hypertext
file specifying the contents of the online help,
given in slide [help-text],
you see that the drawtool command defined
in section [new]
is employed to create the embedded widget.
}
\c{
When specifying the hypertext file,
widgets may be
given a pathname relative to the pathname of the
hypertext widget by using the variable this.
In addition, the hypertext widget offers the variables
thisline and thisfile to identify the
current line number and current file name.
}
\c{
Any of the widgets and commands offered by Tcl/Tk or supported
by hush may be included in a hypertext file,
including the ones defined by the program
itself.
}