The drawtool application

Instructor's Guide


drawtool, design, specification, summary, Q/A, literature


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 to construct compound widgets.

A structural view of the drawtool application is given in slide drawtool-structure.



slide: A (partial) class diagram

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.



slide: Widget containment

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

   path ::= '.' | '.'string | path'.'string
  
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. With respect to the path name hierarchy, when speaking of ancestors we simply mean superordinate widgets. 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 menubar, 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.

slide: An interaction diagram

A typical interaction (or use case) with drawtool is depicted in slide drawtool-interact. On selecting the circle menu entry (or toolbox button), the circle handler is activated to assist in the drawing of a circle. Details will be given when discussing the tablet widget.

The toolbox component

As the first component of drawtool, we will look at the toolbox. The toolbox is a collection of buttons packed in a frame.
  import hush.dv.api.*;
  import hush.dv.widgets.frame;
  
  public class toolbox extends frame {  
toolbox
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() { tablet.mode(_event.arg(1));
reset tablet mode
return OK; } };

slide: The toolbox class

Each button is an instance of the class toolbutton.
  import hush.dv.api.*;
  import hush.dv.widgets.button;
  
  public class toolbutton extends button { 
toolbutton
public toolbutton(widget w, String name) { super(w,name); text(name); bind(w,name); pack("-side top -fill both -expand 1"); } };

slide: The toolbutton class

When a toolbutton is created, the actual button is given the name of the button as its path. Next, 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. The constructor further creates the five 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. 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.

The menubar component

The second component of our drawing tool is the menubar.
  import hush.dv.api.widget;
  
  public class menubar extends hush.dv.widgets.menubar { 
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 menubar

The class menubar, given above, 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.

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 second button of the menubar is defined by the EditMenu. The EditMenu 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 EditMenu coincide.



slide: Tablet

The tablet component

The most important component of our drawtool application is defined by the tablet widget class given below.
  import hush.dv.api.*;
  import hush.dv.widgets.*;
  
  public class tablet extends canvas { 
tablet
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; 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); 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}

The various modes supported by the drawing tool are enumerated as final constants. 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. 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 (super). By convention, when the options parameter is * 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 and bind invocations to the subordinate canvas widget. It then binds itself to be its own handler, which results in binding itself to be the handler for the canvas component. Note that reversing the order of calling redirect and bind 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.

The drawtool class

Having taken care of the basic components of the drawing tool, that is the toolbox, menubar 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 these, but look at the definition of the drawtool class instead. In particular, 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.
  import hush.dv.api.*;
  import hush.dv.widgets.frame;
  import hush.dv.widgets.canvas;
  
  public class drawtool extends canvas { 
drawtool
widget root; tablet tablet; public drawtool() { System.out.println("meta handler created"); } public drawtool(String p, String options) { super(p,"*"); // create empty tablet init(options); } 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)); 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 } };

slide: The drawtool class

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() 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. 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 eval is invoked for self(), with the remaining arguments flattened to a string. This allows for using the drawtool almost as an ordinary canvas.
  	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();
  

slide: The drawtool application

In the program fragment above, the Tcl command drawtool is declared, with an instance of drawtool as its handler. (It is assumed that the tk variable refers to an instance of kit.) In this way, the drawtool widget is made available as a command when the program is used as an interpreter. In this case, the actual drawtool widget is made the handler of the command, to allow for a script to address the drawtool by calling drawtool self.