Program structure


intro Tcl/Tk programs handler actions to events widgets graphics appendix
[-<--%--^-->-] 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. 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 off the details of the window environment.

Employing Tcl/Tk from within C++

Imagine that you have written some numerical function, for example a function employing the Newton method for computing the square root. Such a function may be defined as in the function newton:
    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;
    }
  

slide: The newton function

When you have written such a function, you may wish to have a graphical interface to allow you to experiment with possible inputs in a flexible way. For example, you may wish to have a slider for setting the input value and a message widget displaying the outcome of the function. Such an interface may look like the one in figure Interface.

slide: A graphical interface for newton

Admittedly, the newton function given above is simple enough to be implemented directly in Tcl. Nevertheless, since C++ is to be considered superior for implementing numerical functions, we decide to implement the Newton function in C++ and the graphical interface in Tcl. The problem we need to solve then is how to connect the graphical interface with the C++ code.

The Tcl script

Let us start by defining the interface, where we will use a dummy function to generate the output. A Tcl script defining our interface is given below:
    #!/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 }
  

slide: A Tcl/Tk script

The script defines a slider, as a (horizontal) scale widget, and a message widget, that is used to display the output. The built-in Tcl/Tk bind function is used to associate the movement of the slider with the invocation of the Tcl function generate. Note that the function generate is a dummy function, which merely echos the value of the scale widget to the message widget. Now we have developed a graphical interface, which may be tested by using the hush shell or wish. Next, we need to develop the C++ program embodying the numerical function and connect it to the interface written in Tcl.

The C++ code

The structure of this program is best explained in a number steps. Each of these steps corresponds with a code fragment. Together, these fragments form the C++ program shown below. We will first look at the code. Afterwards it will be explained why the individual fragments are needed.
    // 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();
    }
    
  

slide: The C++ program

The functional part is represented by the function newton. We need to declare its type to satisfy the compiler. Further we need to include the 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.

Comments

The example C++ program illustrates a number of features, some of which are typical for hush and some of which are due to programming in a window environment. In an ordinary C++ program the function main is used to start the computation. Control is effected by creating objects and calling the appropriate functions. When programming a window-based application, at a certain moment control is delegated to the window environment. Consequently, there needs to be some kind of main loop which waits for incoming events, in response to which the control may be delegated to an appropriate component of the program. To hide the details of activating the main loop and the dispatching of events, the hush library provides a class session that allows you to define an application class to initialize your program. In order to respond to events, the hush library provides a handler class, that allows you to associate a C++ object with a Tcl function. Each time the corresponding Tcl function is invoked, the operator() function of the object is called. The actual object is an instance of a derived class, redefining the virtual operator() function of the handler class. Handler classes are typical for hush. Another feature typical for hush is the use of a kit object, that may be accessed by using the tk instance variable of the handler object. The kit object provides access to the Tcl interpreter embedded in the C++ program. In the example it is used to initialize the graphical interface by reading a script file and to define the association between the Tcl function generate and the C++ instance of generator. The widgets defined in the Tcl script are accessed in the C++ program by means of a scale and message pointer. The hush library provides for each Tk widget a class of the same name. Note that not the widgets themselves are created in the constructor of the generator class, but only abstract widget objects that are casted to the appropriate widget types. Casts are needed to access these objects as respectively a scale and message widget. Widgets can be created, however, directly in C++ as well, by employing the appropriate widget class constructors. See section User. As a final comment, the example illustrates a classical stratagem of software engineering, namely the separation of concerns. On the one hand we have a script defining the interface that may be independently tested, and on the other hand we have C++ code embodying the real functionality of our program.

An overview of the hush class library

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, respectively.

The library

The hush C++ library consists of three kinds of classes, namely the widget classes which mimic the functionality of Tk, the handler classes, which are involved in the handling of events and the binding of C++ code to Tcl commands, and the classes kit and session, which encapsulate the embedded interpreter and the window management system,

slide: The hush widget classes

In the widget class hierarchy depicted in figure Widgets, the class widget 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. As shown in the example in the previous section, the widget class allows 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 the appendix.

slide: Hush handler classes

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. Recall that in the example we defined the generator class as a descendant of handler. The handler class has two pre-defined descendant classes, namely the widget class and the class item. This implies, indeed, that both the widget and the item class (that is treated in section Items) may be used as ancestor handler classes as well. 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.

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 below substantiates this claim, albeit in a perhaps disappointingly simple way.
    
    #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
    }
  

slide: A simple interpreter

The structure of the program is similar to the C++ example of section Example. In addition to including the hush.h header file, however, we must include the declarations needed for employing the external hypertext (ht) and distributed processing (dp) packages. Next, we need to define an application class, derived from session, specifying how the hush interpreter deals with command-line arguments and what initialization must take place before starting the main event loop. At this stage it suffices 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 Hypertext, an example will be given that involves the hypertext widget. Further, a predefined button .quit is packed to the root widget. The hush interpreter defined by the program extends the wish interpreter by loading the distributed processing extension 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. The Tcl interface script given in section Script, for example, may be executed using the hush interpreter.

The kit class

Hush is meant to provide a simple 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 (treated in section 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 article. However, since an instance of kit is used in almost any other object (class), it is presented here first. The reader will undoubtly gradually learn the functionality of kit by studying the examples. The class interface of kit is given below: ( Each function listed in the class interface is public unless it is explicitly indicated as protected. The interface descriptions start with the pseudo-keyword interface. This is merely done to avoid the explicit indication of public for both the ancestor and the member functions of the class. )
kit interface">
    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
    };
  

slide: The

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. The first group of functions (eval, result, evaluate and source) may be used to execute commands in 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 result() may be used to fetch the result of the last Tcl command. In contrast, the function result(char*) may be used to set the result of a Tcl command, when this command is defined in C++ (as may be done with kit::bind). The function evaluate provides a shorthand for combining eval and result(). The function source may be used to read in a file containing a Tcl script. Also, we have the kit::bind function that may be used to associate a Tcl command with a handler object. The next group of functions is related to widgets. 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 Configure. 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 enforce that all pending events are processed. For example, when moving items on a canvas, an update may be needed for making the changes visible. Also, we have a function send 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. Further, we have the functions trace and notrace, which may be used to turn on, respectively off, tracing. The level indicates in 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.

The session class

Each program written with hush may contain only one embedded hush 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. The preferred way of doing this is by defining a descendant class of the session class, redefining the virtual function session::main 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 the code for the interpreter in section Interpreter.
    interface session  { 
      session(int argc, char** argv, char* name = 0);
      
      virtual void prelude();  
      virtual void main();
      int run( );
    protected:
      kit* tk;
    };
  

slide: The session class

When creating a session object, the name of the application may be given as the last parameter. By this name the application is known to other Tk applications, that may communicate with each other by means of the send command. Apart from the function main, also a function prelude may be defined. When the program is used as an interpreter (by giving -f file as command line arguments) only the prelude function will be executed, otherwise prelude will be executed before main. In interpreter mode, the function main 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.
intro Tcl/Tk programs handler actions to events widgets graphics appendix