Version 2.0
A. Eliëns (eliens@cs.vu.nl)This article describes the C++ programmer interface to hush, the hyper utility shell based on Tcl/Tk. Tcl is a scripting language that may be embedded in C or C++. Tk is a window and graphics toolkit based on X11, with an associated interpreter called wish. The hush library contains classes that provide convenient yet flexible access to the functionality offered by the Tcl/Tk toolkit and its extensions. The library is intended to support the needs of both novice and experienced (window) programmers. It offers widget and graphics classes with an easy to use interface, but allows more experienced programmers also to employ the Tcl scripting language to define the behavior and functionality of widget and structured graphics objects. The design of the hush library has been inspired by the InterViews library. However, both the use of event callbacks and the functional interface of widget and graphics classes is significantly simpler. An important advantage of basing hush on the Tk toolkit is that existing Tk applications written for the Tk interpreter wish can easily be (re)used in a C++ context, virtually without any costs. On the other hand, programs employing hush may again be used as an enhanced version of the wish interpreter, allowing the functionality defined in the program to be used in a (hush) script.
A number of toolkits for the X11 environment with an interface to C++ do exist already. Well-known for example is the InterViews library, which offers powerful features for defining the layout of graphical user interfaces. See LVC89. However, despite the elegance of its design, InterViews is slightly cumbersome to use and lacks a number of the features and widgets needed for rapidly implementing a graphical user interface. Commercial packages for GUI (Graphical User Interface) programming in C++ are available. The disadvantage of these packages, apart from their price, is primarily that they do not offer the flexibility needed in a research environment.
A rather different approach to GUI programming has been advocated in Ousterhout91, which describes the Tcl/Tk toolkit. Tcl is a cshell-like (interpreted) script language that may be embedded in C or C++. Tk is a window and graphics toolkit based on X11, partly implemented in Tcl and partly in C. Tk offers numerous widgets, including a powerful canvas and text widget. Moreover, the Tcl scripting language allows the user to rapidly prototype rather complex graphical user interfaces by writing Tcl scripts. These scripts may be executed by using wish, the windowing shell interpreter that comes with Tk. Despite being based on Tcl, the performance of Tk (and wish) is comparable with (and in some respects even better than) C or C++ based toolkits.
The Tcl/Tk toolkit has become very popular in a rather short period of time. The popularity of Tcl/Tk is partly due to the extensibility of Tcl. New functionality, implemented in C, may easily be added by creating a new version of the wish interpreter, incorporating the additional commands. Numerous extensions to Tcl/Tk and corresponding interpreters have been made available, including extensions offering facilities for distributed programming (dp), extensions offering object oriented features ()\footnote{ may be read as a paraphrase of tcl++ in Tcl syntax. }, and extensions offering additional widgets such as a barchart and hypertext widget (blt).\footnote{ These extensions may be obtained from harbor.ecn.purdue.edu. }
The possibility of employing interpreted code and the availability of numerous widgets makes the Tcl/Tk toolkit (and its extensions) an ideal vehicle for implementing user interfaces. However, Tcl/Tk has its drawbacks as well. One problem, obviously, is to manage the large number of extensions. Ideally, there is one wish-like shell unifying the various features. Even better, one should have the opportunity to create such a shell in a simple manner. A second problem is that, when an application grows, script code will not always allow for an optimal solution. Generally, script code is not robust and may be hard to maintain. In particular, when an application contains many components not related to the user interface, efficiently compiled code may be more appropriate. For the latter problem, the obvious solution is to employ the C API (Application Programmer Interface) offered by Tcl and to create a new interpreter including the functionality needed. In a similar way, the first problem is rather easily solved by linking the appropriate libraries into an extended interpreter. Nevertheless, this is easier said than done. First of all, the C API offered for Tcl/Tk is rather demanding for the novice programmer and does not support a style of programming that is recommendable from a software engineering perspective. Secondly, although not very difficult, creating a new interpreter with additional C/C++ code is somewhat cumbersome. The hush library has been developed to address the two problems mentioned. Hush stands for hyper utility shell. The standard interpreter associated with the hush library is a shell, called hush, including a number of the available extensions of Tcl/Tk and widgets developed by ourselves (such as a filechooser and an MPEG video widget). The hush library offers a C++ interface to the Tcl/Tk toolkit and its extensions. It allows the programmer to employ the functionality of Tcl/Tk in a C++ program. Moreover, a program created with hush is itself an interpreter extending the hush interpreter (and wish). The hush library is explicitly intended to support the needs of both novice and experienced window programmers. Its C++ class interface should suffice for most applications, yet it allows for employing Tcl script code when more is demanded. The contribution of hush with respect to the Tcl/Tk toolkit is essentially that it provides a type-secure solution for connecting Tcl and C++ code. As an additional advantage, the hush library allows the programmer to employ inheritance for the development of possibly compound widgets. In particular, it provides the means to define composite widgets that behave as the standard Tk widgets. The class structure of the hush library is reminiscent to the class structure of the InterViews library. In comparison with the InterViews library, the widget class interfaces and event callbacks are significantly easier to use. Also, the hush library provides many more ready-to-use graphical interface widgets. However, hush does not offer resolution-independent graphics and provides no pre-defined classes for complex interactions. Summarizing, hush supports a multi-paradigm approach to window programming, allowing to combine the robustness of compiled C++ code with the flexibility of interpreted Tcl code. As such, it offers the best of both worlds. Or the worst, for that matter.
The material presented here requires at least some
knowledge of C++. See for example Stroustrup91.
Some familiarity
with Tcl/Tk is also helpful. See Ousterhout94.
Availability
The hush library has been in use for student programming
assignments at the Vrije Universiteit for two years.
It may be obtained by anonymous ftp from
ftp.cs.vu.nl
,
directory eliens/hush
.
You may also retrieve it via
www.cs.vu.nl/~eliens/hush/
.
Structure
In section Background,
some background information concerning Tcl/Tk is given.
Section Structure
sketches the structure of a typical
hush program
and gives an overview of the hush
class library, including the kit and session class.
Section Binding
describes how handler objects may be defined as
event callbacks.
Next, section User
presents a drawing tool application.
The application illustrates the use
of the various widget classes
and demonstrates how to construct compound
widgets.
In addition, it shows how a widget developed
in C++ may be made available
as a widget command to be used in scripts.
And finally, in section Graphics,
we will look at the facilities offered
for structured graphics and hypertext.
button .b -text "hello world" -command { puts "hello world" } pack .b
// Define a command function in C style int aCommand( ClientData data, Tcl_Interp* interp, int args, char* argv[]) { some_type* x = (some_type*) data; // conversion by cast // some processing } // Declare the function aCommand as a Tcl command // for example in the main function some_type* user = new some_type(); // to create the client data Tcl_CreateCommand( interp, "aco", aCommand, (ClientData) user );
double newton( double arg ) { // computes square root double r=arg, x=1, eps=0.0001; while( fabs(r - x) > eps ) { r = x; x = r - (r * r - arg) / (2 * r); } return r; }
#!/usr/prac/se/bin/hush -f proc generate {} { .m configure -text [.s get] } scale .s -label "seed" -orient horizontal -relief sunken message .m -width 256 -aspect 200 pack .m .s -fill x bind .s <Any-ButtonRelease> { generate }
// Initial declarations #include "hush.h" double newton(double arg); // declare the function char* ftoa( double f); // to convert float to char* // The generator (handler) class gives access to the widgets class generator : public handler { public: generator() { // access to Tcl widgets s = (scale*) new widget(".s"); m = (message*) new widget(".m"); } ~generator() { s->destroy(); m->destroy(); // to destroy widgets delete s; delete m; // to reclaim resources } int operator()() { // the generator action float f = s->get(); m->text( ftoa( newton(f) ) ); // display value return OK; } private: scale* s; message* m; }; // The application class takes care of installing the interface class application : public session { public: application(int argc, char* argv[]) : session(argc,argv,"newton") {} void main( ) { // tk is an instance variable tk->source("interface.tcl"); // read interface script handler* g = new generator(); tk->bind("generate",g); // bind Tcl command } }; // Finally, the function main is defined void main (int argc, char **argv) { session* s = new application(argc,argv); s->run(); }
hush.h
header file and declare
an auxiliary function ftoa
that is used to convert floating point
values to a string.
The next step
involves the definition of the
interfacing between the Tcl code
and the C++ program.
The class generator defines
a so-called handler object that will
be associated with the function
generate employed in the script,
overriding the dummy Tcl function generate
as defined in the script.
In order to access the scale and message widget
defined for the interface, C++ pointers to
these widgets are stored in instance
variables of the object.
These pointers are initialized when
creating a generator object.
The widgets
are destroyed when deleting the object.
Note that the widgets must first
be destroyed before deleting the corresponding C++
objects.
All you need to know at this stage is
that when the function generate
is called in response to moving
the slider,
or more precisely releasing the mouse
button,
then the operator() function of the
C++ generator object is called.
In other words, the operator()
function is (by convention)
the function that is executed
when a Tcl command that is bound
to a handler object is called.
The generator::operator() function
results in displaying
the outcome of the newton function,
applied to the value of the slider,
in the message widget.
Then we
define an application class,
which is needed for the program to
initialize the X-windows main event loop.
An application class must be
a subclass of the session class.
To initialize the program,
the application class redefines
the (virtual) function main
inherited from session.
The function application::main takes
care of initializing the interface,
creates an instance of the generator
class,
and binds the Tcl command generate
to the generator object.
Finally, the function main is defined.
A function main is required for each C or C++
program.
It consists merely
of creating an instance of the application class and the
invocation of run, which starts
the actual program.
#include "hush.h" // Include definitions of external package(s) #include "extern/ht.h" // Define the application class class application : public session { public: application(int argc, char* argv[]) : session(argc,argv) { hyper = 0; if ((argc==3) && !strcmp(argv[1],"-x")) { // check for -x hyper = 1; strcpy(hyperfile,argv[2]); } } void main() { // tk represents the kit init_ht(tk); // initialize package(s) if (hyper) { // initialize hypertext hypertext* h = new hypertext(".help"); h->file(hyperfile); h->geometry(330,250); h->pack(); tk->pack(".quit"); // predefined button to quit } } private: char hyperfile[BUFSIZ]; int hyper; }; // Define the main function int main (int argc, char* argv[]) { session* s = new application(argc,argv); s->run(); // start X event loop }
interface kit { int eval(char* cmd); // to evaluate script commands 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 void bind(char* name, handler* h); // to bind Tcl command widget* root(); // returns toplevel (root) widget widget* pack(widget* w, char* options = "-side top -fill x"); widget* pack(char* wp, char* options = "-side top -fill x"; 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 };
interface session { session(int argc, char** argv, char* name = 0); virtual void prelude(); virtual void main(); int run( ); protected: kit* tk; };
#include "hush.h" // The drawing_canvas responds to press, motion and release events class drawing_canvas : public canvas { public: drawing_canvas( char* path ) : canvas(path) { geometry(200,100); bind(this); // become sensitive dragging = 0; } void press( event& ) { dragging = 1; } void motion( event& e) { if (dragging) circle(e.x(),e.y(),1,"-fill black"); } void release( event& ) { dragging = 0; } protected: int dragging; }; // To initialize the application class application : public session { public: application( int argc, char* argv[] ) : session(argc,argv,"draw") {} void main() { canvas* c = new drawing_canvas(".draw"); c->pack(); tk->pack(".quit"); } }; int main (int argc, char* argv[]) { session* s = new application(argc,argv); return s->run(); }
class widget : public handler { public: ... void bind(class handler* h) { ... } ... };
interface event { int type(); // X event type char* name(); // type as string int x(); int y(); int button(int i = 0); // ButtonPress int buttonup(int i = 0); // ButtonRelease int motion(); // MotionNotify int keyevent(); // KeyPress or KeyRelease int buttonevent(int i = 0); // ButtonPress or ButtonRelease int keycode(); void trace(); // prints event information void* rawevent(); // delivers raw X event };
interface handler { virtual event* dispatch(event* e); 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: event* _event; kit* tk; };
event* handler::dispatch( event* e ) { _event = e; int res = this->operator()(); return (res != OK) ? _event : 0; } int handler::operator()() { event& e = * _event;fetch 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; }
class application : public session { public: application(int argc, char* argv[]) : session(argc,argv,"drawtool") {} void main() { widget* root = tk->root(); frame* f = new frame(root,".frame"); tablet* c = new tablet(f); // create tablet toolbox* b = new toolbox(f,c); menubar* m = new menu_bar(root,c,b); b->pack("-side left"); // pack tablet c->pack("-side right"); tk->pack(m)->pack(f); // pack menu and frame } };
path ::= . | .string | path.stringwhere string and path are nonterminals. For example "." is the pathname of the root widget, whereas ".quit" is the pathname of a child of the root widget. A widget that is a child of another widget must have the pathname of its parent as part of its own path name. For example, the widget ".f.m" may have a widget ".f.m.h" as a child widget. Note that the widget hierarchy induced by the pathnames is completely orthogonal to the widget class inheritance hierarchy depicted in figures Widgets and Classes. Pathnames are treated somewhat more liberally in hush. For example, widget pathnames may simply be defined or extended by a string. The missing dot is then automatically inserted.
interface widget : handler { widget(char* p); widget(widget& w, char* p); char* type(); // returns type of the widget char* path(); // returns path of the widget int eval(char* cmd); // invokes "thepath() cmd" char* result(); // returns the result of eval char* evaluate(char* cmd); // combines eval and result() virtual void configure(char* cmd); // invokes Tk configure virtual void geometry(int w, int h); // determines w x h widget* pack(char* options = "" ); // maps it to the screen bind(char *b, handler* h, char* args = "" ); // binding bind(handler* h, char* args = "" ); // implicit void xscroll(scrollbar* s); // to attach scrollbars void yscroll(scrollbar* s); void focus(char* options=""); void grab(char* options=""); void destroy(); // to remove it from the screen void* tkwin(); // gives access to Tk_Window implementation widget* self(); // for constructing mega widgets void redirect(widget* w); protected: char* thepath(); // delivers the virtual path void alias( widget* ); // to create widget command virtual install(binding*,char* args=""); // default bindings virtual direct(char* bnd, binding*, char* args=""); // effect };
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. If a widget with that name exists, it may be treated as an ordinary widget object, otherwise an error will occur. The constructor widget(widget* w,char* path) creates a widget by appending the pathname path to the pathname of the argument widget w. The function path delivers the pathname of a widget object. Each widget created by Tk actually defines a Tcl command associated with the pathname 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 blueThe 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.
self()->path()
.
In contrast, the function path will still deliver
the pathname 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.
The alias function is needed when creating
widgets that are also used in Tcl scripts.
It creates the command corresponding to the
widget's path name.
How redirect and alias actually work will
hopefully become clear in the examples.
class toolbutton : public button { // the toolbutton public: toolbutton(widget* w, char* name) : button(w,name) { text(name); bind(w,name); // the parent becomes the handler pack(); } }; class toolbox : public frame { // the toolbox public: toolbox(widget* w, tablet* t) : c(t), frame(w,"toolbox") { button* b0 = new toolbutton(this,"draw"); 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()() { c->mode( _event->arg(1) ); // transfer to tablet return OK; } private: tablet* c; };
class menu_bar : public menubar { // row of menubuttons 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); } };
class file_menu : public menubutton { public: file_menu(widget* w, tablet* t) : c(t), menubutton(w,"file") { configure("-relief sunken"); text("File"); pack("-side left"); f = new file_handler(c); // create a file_handler class menu* m = new class menu(this,"menu"); this->menu(m); // declares it for the menubutton m->bind(this); // installs this as the handler m->entry("Open"); m->entry("Save"); m->entry("Quit"); } int operator()() { if (!strcmp( _event->arg(1),"Quit")) tk->quit(); else f->dispatch( _event ); // transfer to file_handler return OK; } protected: tablet* c; file_handler* f; };
class drawmode { // drawing modes public: enum { draw, move, box, circle, arrow, lastmode }; }; class tablet : public canvas { // the tablet public: tablet(widget* w, char* options=""); int operator()() { // according to _mode return handlers[ mode] ->dispatch( _event ); } void mode(char* m); // to set the drawing mode protected: void init(char* options); // initializes the tablet int _mode; class handler* handlers[drawmode::lastmode]; // keeps modes canvas* c; // the actual canvas };
tablet::tablet(widget* w, char* options) : canvas(w,"tablet",0) { widget* top = new frame(path()); init(options); // inialization, layout redirect(c); // redirect to canvas bind(this); // this is the handler handlers[drawmode::draw] = new draw_handler(this); 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::draw; }
class application : public session { public: application(int argc, char* argv[]) : session(argc,argv,"drawtool") {} void prelude( ) { tk->bind("drawtool", new drawtool()); // declare } void main( kit* tk, int, char* argv[] ) { drawtool* d = new drawtool(".draw"); tk->bind("drawtool",d); // override d->pack(); } };In the body of the prelude function, the Tcl command drawtool is declared, with an instance of drawtool as its handler. In this way, the drawtool widget is made available as a command when the program is used as an interpreter. However, in the function main this declaration is overridden. Instead, the actual drawtool widget is made the handler of the command, in order to allow for a script to address the drawtool by calling drawtool self, as will be explained later.
Since an instance of drawtool may also be used as simply a handler for the drawtool command, the drawtool class must offer a constructor that creates no widget, in addition to a constructor that does create a drawtool widget:
class drawtool : public canvas { public: drawtool() : canvas() { } // no widget drawtool(char* p, char* opts="") : canvas(p,0) { top = new frame(path(),"-class Drawtool"); // outer frame init(opts); redirect(c); // redirect to tablet alias( top ); // to declare widget command } // Define the semantics of the drawtool command int operator()(){ if (!strcmp("self",argv[1]) ) // self tk->result(self()->path()); else if ( !strcmp( "drawtool" ,*argv) ) // create create(--argc,++argv); else // eval self()->eval( flatten(--argc,++argv) ); return OK; } protected: wiget* top; // outer frame tablet* c; // inner component void init(char* options); // To create a new drawtool widget and corresponding command void create(int argc, char* argv[]) { char* name = *argv; new drawtool(name, flatten(--argc,++argv)); } };
set x [drawtool self] \$x create rectangle 100 20 160 80 \$x create rectangle 90 30 150 90 \$x create oval 120 40 170 90Evaluating the script results in the drawing displayed in figure Drawtool. Such a script may be read in by using the Open option in the File menu (see section Dialogs).
If neither of these cases apply, the function widget::eval is invoked for self(), with the remaining arguments flattened to a string. This makes it possible to use the drawtool almost as an ordinary canvas as illustrated above and in the example hypertext script shown in section Hypertext. The creation of the actual widget and declaration of the corresponding Tcl command, according to the Tk convention, is somewhat more involved. 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 pathname.
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.
class file_chooser : public toplevel { // toplevel file_chooser() : toplevel( gensym("filechooser") ) { init(); } int operator()(); char* get() { return e->get(); } protected: button* b; button *c; // OK and CANCEL entry* e; listbox* l; int install(char* s, binding* a, char* opts); void init(); void list(); };The file_chooser widget consists of a listbox filled with filenames and an entry widget that contains the filename selected by the user (by double clicking on the name) or which may, alternatively, be used to type in a filename directly. In addition, the file_chooser has an OK button, to confirm the choice and a CANCEL button, to break off the dialog. Typically, a file_chooser is a toplevel widget, that is a widget that is independently mapped to the screen. To avoid name clashes the function gensym, which delivers a system-wide unique name (with filechooser as a prefix), is used to determine its path. Apart from the operator() function, the file_chooser has only one public function get, which delivers the name selected or typed in by the user. The widget components of the file_chooser, two buttons and the entry and listbox widgets, are stored in its instance variables. Further, we have a function init to construct the actual file_chooser widget, a function list to fill the listbox and the function install, which is used to install an external handler for the two button widgets. The install function is defined as
void file_chooser::install(binding* a, char* args) { b->handler(a,args); c->handler(a,args); }Recall, that when declaring a handler for a button, the name of the button is given as an additional argument when invoking the handler. This enables the file_handler to distinguish between a call due to pressing the OK button and a call due to pressing the CANCEL button. The interplay between the C++ definition and the underlying Tcl/Tk toolkit is nicely illustrated by the definition of the list function.
void file_chooser::list() { sprintf(buf,"foreach i [glob *.tcl] { %s insert endCalling list results in filling the listbox with the filenames in the current directory. Its corresponding definition in C++ would, no doubt, be much more involved.
class file_handler : public handler { public: file_handler( canvas* x ) : c(x) {} int operator()() { char* key = _event->arg(1); if (!strcmp("Open", key)) launch("OPEN"); else if (!strcmp("Save", key)) launch("SAVE"); else if (!strcmp("OPEN", key)) open(); else if (!strcmp("SAVE", key)) save(); return OK; } protected: canvas* c; file_chooser* f; void launch(char* args) { // launch new filechooser f = new file_chooser(); f->handler(this, args); } void open() { tk->source( f->get() ); f->destroy(); } void save() { c->postscript( f->get() ); f->destroy(); } };
interface item { operator int(); // returns item index void configure(char* cmd); // calls canvas::itemconfigure void tag(char* s); // sets tag for item char* tags(); // delivers tags set for the item void move(int x, int y); bind(char *b, handler* h, char* args = "" ); bind(char *b, binding* ac, char* args = "" ); handler(class handler* h, char* args = "" ); handler(binding* ac, char* args = "" ); protected: virtual install(action&,char* args=""); // default bindings };
class move_handler : public handler { public: move_handler( canvas* cv ) : c(cv) { dragging = 0; } void press( event& e ) { // if overlapping start dragging x = e.x(); y = e.y(); id = c->overlapping(x, y); if (id) dragging = 1; } void motion( event& e ) { // if dragging move if (dragging) { id.move( e.x() - x, e.y() - y ); x = e.x(); y = e.y(); } } void release( event& ) { dragging = 0; } // stop dragging protected: canvas* c; int dragging; item id; int x,y; };
void box_handler::motion( event& e ) { // if dragging stretch if (dragging) { id.del(); id = c->rectangle(x,y,e.x(),e.y()); // x and y are fixed } }
%%
for both the begin and end of the code.
A screen shot of a fragment of the on-line help
for drawtool is given in
figure Help.
Notice, that the on-line 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 on-line help,
given below,
you see that the drawtool command defined
in section new
is employed to create the embedded widget:
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.
interface scale : widget { scale(char* p, char* options = ""); scale(widget* w, char* p, char* options = ""); void text(char* s); // text to display void from(int n); // begin value void to(int n); // end value int get(); // gets the value void set(int v); // sets the value protected: install(binding*, char* args); };
interface message : widget { message(char* p, char* options = "" ); message(widget* w, char* p, char* options = "" ); void text(char* s); // the text };
interface button : widget { button(char* p, char* options = ""); button(widget* w, char* p, char* options = ""); void text(char* s); // text to display void bitmap(char* s); // to display a bitmap void state(char *s); // to change the buttons state void flash(); char* invoke(); protected: install(binding*,char* args); // default binding };
interface menubutton : button { menubutton(char* p, char* options = ""); menubutton(widget* w, char* p, char* options = ""); void menu(char* s); // to attach a menu void menu(class menu* m); };
interface menu : widget { menu(char* p, char* options = ""); menu(widget* w, char* p, char* options = ""); menu* add(char* s, char* options = ""); menu* entry(char* s, char* args ="", char* options=""); menu* entry(char* s, binding* ac, char* args="", char* opts=""); menu* cascade(char* s, char* m, char* options = ""); menu* cascade(char* s, menu* m, char* options = ""); char* entryconfigure(int i, char* options); int index(char *s); int active(); // returns active index void del(int i); // delete entry with index i void del(char* s); // delete entry with tag s char* invoke(int i); // invoke entry with index i char* invoke(char *s ); // invoke entry with tag s void post(int x = 500, int y = 500); void unpost(); protected: install(binding*, char* args); };
interface canvas : widget { canvas(char *p, char* options=""); canvas(widget* w, char *p, char* options=""); void tag(int id, char* tag); char* tags(int id); void move(int id, int x, int y); void move(char* id, int x, int y); item bitmap(int x1, int y1, char* bitmap, char* options=""); item line(int x1, int y1, int x2, int y2, char* options=""); item line(char* linespec, char* options=""); item circle(int x1, int y1, int rad, char* options=""); item oval(int x1, int y1, int x2, int y2, char* options=""); item polygon(char* linespec, char* options=""); item rectangle(int x1, int y1, int x2, int y2, char* options=""); item text(int x1, int y1, char* txt, char* options=""); item window(int x1, int y1, char* win, char* options=""); item window(int x1, int y1, widget* win, char* options=""); item current(); item overlapping(int x, int y); itemconfigure(int it, char* options); itemconfigure(char* tag, char* options); itembind(int it, char* s, binding* a, char* args = "" ); itembind(char* tag, char* s, binding* a, char* args = "" ); void postscript(char* file, char* options=""); protected: install(binding*, char* args); };
interface frame : widget { frame(char* p, char * options = ""); frame(widget* w, char* p, char * options = ""); };
interface scrollbar : widget { scrollbar(char* p, char* options = ""); scrollbar(widget* w, char* p, char* options = ""); void orient(char* opts="vertical"); // orientation xview(widget* w); // widget to scroll yview(widget* w); // widget to scroll };
interface listbox : widget { listbox(char* p, char* options = ""); listbox(widget* w, char* p, char* options = ""); void insert(char* s); char* get(int d); // entry with index d void singleselect(); protected: install(binding*, char* args); };
interface entry : widget { entry(char* p, char* options = ""); entry(widget* w,char* p, char* options = ""); void insert(char* s); // insert text char* get(); // to get the text protected: install(binding*, char* args); };
interface hypertext : widget { hypertext(char* p, char* options = ""); hypertext(widget* w, char* p, char* options = ""); void file(char* f); // to read in hypertext file };