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 90
Evaluating 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 end
Calling 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(); }
};