The hush class library

The hush C++ library ( [Elines94]) consists of three kinds of classes,  [Ousterhout90], namely (a) the widget classes,  [Ousterhout90,Ousterhout91], 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, 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 Java or 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. 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.

User interface widgets

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.

slide: The drawtool interface

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.
  import hush.dv.api.*;
  import hush.dv.widgets.Frame;
  import hush.dv.widgets.Canvas;
  
  public class DrawTool extends Canvas implements Application {
  
  Widget root;
  Tablet tablet;
  
  public DrawTool() { System.out.println("meta handler created"); } 
  
  public DrawTool(String p, String options) {
          super(p,"*");                 // create empty button
  	init(options);
  	debug("DrawTool created");
  	}
  
  public int operator() {
  	System.out.println("Calling DrawTool:" + _event.args(0) );
  	String[] argv = _event.argv();
  	if ("self".equals(argv[1])) tk.result(self().path());
  	else if ("drawtool".equals(argv[0]))
  		create(argv[1],_event.args(2)); // replaces quote
  	else if ("path".equals(argv[1])) tk.result(path()); 
  	else if ("pack".equals(argv[1])) pack(_event.args(2));
  	else self().eval( _event.args(1) );  // send through
  	return OK;
  	}
  
  void create(String name, String options) {
  	DrawTool m = new DrawTool(name,options);
  	}
  
  void init(String options) {
  	root = new Frame(path(),"-class Meta");
  
  	Frame frame = new Frame(root,"frame");
  
  	tablet = new Tablet(frame,"tablet",options);
  
  	ToolBox toolbox = new ToolBox(frame,tablet);
  	MenuBar menubar = new MenuBar(root,tablet,toolbox);
  
          toolbox.pack("-side left -fill y -expand 0");
  	tablet.pack("-side left -fill both -expand 1");
  
  	menubar.pack();
  	frame.pack("-expand 1 -fill both");
  
  
  	redirect( tablet );  // the widget of interest
  
  	alias( this );  // to install the widget command
  	}
  
  

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.

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


  package hush.dv.api;
  
  public class widget extends Handler {
  
  public widget()  { }
  widget(int r) { _self = r; }
  
  public native String path();
  public native void eval(String cmd);
  
  public native void title(String s);
  
  public native void pack(String s);
  public void pack() { pack("*"); }
  
  public void bind(handler h) { dobind(h,""); }
  public void bind(handler h,String s) { dobind(h,s); }
  public void bind(String p, handler h) { dopbind(p,h,""); }
  public void bind(String p, handler h,String s) { dopbind(p,h,s); }
  
  public native void dobind(handler h,String s);
  public native void dopbind(String p,handler h,String s);
  
  public native void configure(String cmd);
  public native void geometry(int x, int y);
  
  public native void xscroll(widget w);
  public native void yscroll(widget w);
  
  public widget self() { return new widget( selfid() ); }
  public native void redirect(widget inner);
  public native void alias(widget outer);
  
  public native int selfid();
  };
  

slide: The widget class

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 widget(widget* w,char* path) 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. Naming widgets in a hierarchical fashion does not imply that the widgets behave accordingly. The widget class interface offers two pack functions. The function widget::pack(char*) 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 widget::pack(widget*,char*) 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 kit::pack function may only be used to pack widgets to the root window.

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, ButtonPress, ButtonRelease and 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 widget::handler 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. In addition, the widget class offers four functions that may be used when defining compound or mega widgets. The function redirect(w) must by used to delegate the invocation of the eval, configure, bind and handler functions to the widget w. The function self() 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 self()->path(). 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.

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.
  import hush.dv.api.*;
  import hush.dv.widgets.Frame;
  
  public class ToolBox extends Frame { 
  
  Tablet tablet;
  
  public ToolBox(Widget w, Tablet t) {
      super(w,"toolbox");
      tablet = t;
      new ToolButton(this,"draw");
      new ToolButton(this,"move");
      new ToolButton(this,"box");
      new ToolButton(this,"circle");
      new ToolButton(this,"arrow");
  }
  
  public int operator() {
  	debug("Toolbox " + _event.arg(1));
          tablet.mode(_event.arg(1)); 
          return OK;
          }
  
  };
  

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 operator() 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.
  import hush.dv.api.Widget;
  
  public class MenuBar extends hush.dv.widgets.MenuBar {
  
  public MenuBar(Widget w, Tablet t, ToolBox b)  {
      super(w,"bar");
      configure("-relief sunken"); 
  
      new FileMenu(this,t); 
      new EditMenu(this,b);
      new HelpButton(this); 
      }
  };
  

slide: The {\em menu\_bar}

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.
  import hush.dv.api.*;
  import hush.dv.widgets.Menu;
  import hush.dv.widgets.MenuButton;
  
  public class FileMenu extends MenuButton {
  
  Tablet  tablet;
  FileHandler filehandler;
  
  public FileMenu(Widget w, Tablet t) {
  
  	super(w,"file");
  	tablet = t;
  
  	configure("-relief sunken -text File");
      	text("  File  ");
  	pack("-side left");
  
  	filehandler = new FileHandler(tablet);
  
  	Menu m = new Menu(this,"menu","-tearoff false");
  
      	this.menu(m);
      	m.bind(this);
  
      	m.entry("Open");
      	m.entry("Save");
      	m.entry("Quit");
  	}
  
  public int operator() {
  	if ("Quit".equals(_event.arg(1))) tk.quit();
  	else filehandler.dispatch(_event);
  	return OK;
  	}
  };
  

slide: The {\em file\_menu}

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 menubar::menu 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 kit::quit function (e), the calls are delegated to the previously created {\em file_handler}. 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.
  import hush.dv.api.*;
  import hush.dv.widgets.*;
  
  
  public class Tablet extends Canvas implements Application { 
  
  int _mode;
  Canvas canvas;
  Handler[] handlers;
  
  final int DRAW 	= 0;
  final int MOVE  = 1;
  final int CIRCLE = 2;
  final int BOX = 3;
  final int ARROW = 5;
  
  private Tablet() {   debug("Tablet Application"); }
  
  public Tablet(Widget w, String name, String options) {
  
  	super(w,name,"*");
  
  	handlers = new Handler[12];
  
          init(options);
  
          redirect(canvas); // to delegate to canvas
          bind(this); // to intercept user actions 
  	
          handlers[DRAW] = new DrawHandler(canvas);
          handlers[MOVE] = new MoveHandler(canvas);
          handlers[BOX] = new BoxHandler(canvas);
          handlers[CIRCLE] = new CircleHandler(canvas);
          handlers[ARROW] = new ArrowHandler(canvas);
  
          _mode = 0; // drawmode.draw;
          }
  
  
  public int operator() { 
  	handlers [mode].dispatch(_event);
  	return OK;
  	}
  
  public int mode(String s) { 
  	int m = -1;
  	if ("draw".equals(s)) m = DRAW; 
  	if ("move".equals(s)) m = MOVE; 
  	if ("box".equals(s)) m = BOX; 
  	if ("circle".equals(s)) m = CIRCLE;
  	if ("arrow".equals(s)) m = ARROW; 
  	if (m >= 0) _mode = m;
  	return _mode;
  	}
  
  void init(String options) {
  
          Widget root = new Frame(path(),"-class Tablet");
  
  	canvas = new Canvas(root,"canvas",options);
  	canvas.configure("-relief sunken -background white");
  	canvas.geometry(200,100);
  	//canvas.region(0,0,800,800);
  	
  	Scrollbar scrollx = new Scrollbar(root,"scrollx");
  	scrollx.orient("horizontal");
  	scrollx.pack("-side bottom -fill x -expand 0");
  	
  	Scrollbar scrolly = new Scrollbar(root,"scrolly");
  	
  	scrolly.orient("vertical");
  	scrolly.pack("-side right -fill y -expand 0");
  	
  	canvas.pack("-side top -fill both -expand 1");
  	
  	canvas.xscroll(scrollx); scrollx.xview(canvas);
  	canvas.yscroll(scrolly); scrolly.yview(canvas);
  	}
  };
  

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 operator() 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. 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 operator() function takes care of dispatching calls to the appropriate handler. The dispatch function must be called to pass the tk, argc and argv parameters.

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. Defining a widget command involves three steps: (I) the declaration of the binding between a command and a handler, (II) the definition of the operator() 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 operator() 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 widget::eval is invoked for self(), 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. 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.
  public int main() {   // in the Application class
  	debug("Application.main (Java) ...");
  	tk.trace();
  	Canvas c = new DrawTool("draw","");
  	tk.bind("drawtool",c);
  	c.circle(20,20,20,"-fill red");
  	c.rectangle(30,30,70,70,"-fill blue");
  	c.pack();
  
  	tk.pack("quit");
  	return OK;
  	}
  

slide: The drawtool application

The application class depicted in slide new-command will by now look familiar, except for the function prelude. In the the static main 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.

Graphics and hypertext

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.
  package hush.dv.api;
  
  public class item extends handler {
  
  public native void bind(handler h);
  
  public native void configure(String opts);
  
  public boolean valid() { 
  	int b = isvalid();
  	if (b==0) return false; else return true;
  	}
  
  private native int isvalid();
  
  public native void tag(String s);
  public native String tags();
  
  public native void move(int x, int y);
  public native void del();
  
  public native String coords();
  public native String bbox();
  };
  

slide: The item class

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. 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. In a similar way as for widgets, items may be associated with events, either explicitly by using item::bind, or implicitly by using item::handler. 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.
  import hush.dv.api.*;
  import hush.dv.widgets.Canvas;
  
  public class MoveHandler extends Handler {
  
  int x; int y;
  boolean dragging;
  Canvas _canvas;
  Item it;
  
  public MoveHandler(Canvas c) {
  	_canvas = c;
  	dragging = false;
  	}
  
  public void press(Event ev) {
  	dragging = true;
  	x = ev.x(); y = ev.y();
  	it = _canvas.overlapping(x,y);
  	}
  
  public void release(Event ev) {
  	dragging = false;
  	}
  
  public void motion(Event ev) {
  	if (dragging) {
  	     it.move( ev.x() - x, ev.y() - y );
  	     x = ev.x(); y = ev.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 operator() 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.