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(); } };