public interface widget { widget
public String path();
public void eval(String cmd);
public void pack(String s);
public void bind(handler h,String s);
public void bind(String p, handler h,String s);
public void configure(String cmd);
public void geometry(int x, int y);
public void xscroll(widget w);
public void yscroll(widget w);
public widget self(); // to define compound widgets
public void redirect(widget inner);
};
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 function eval
enables 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.
As an example look at the code for the drawing canvas
widget depicted in slide [drawing-canvas].
import hush.dv.api.event;
import hush.dv.widgets.canvas;
class draw extends canvas {
boolean dragging;
public draw(String path) {
super(path);
dragging = false;
bind(this);
}
public void press(event ev) {
dragging = true;
}
public void release(event ev) {
dragging = false;
}
public void motion(event ev) {
if (dragging)
circle(ev.x(),ev.y(),2,"-fill black");
}
};
The class draw has an instance variable dragging,
that reflects whether the user is actually drawing a figure.
If dragging is true, motions with the mouse
will result in small dots on the screen.
slide: Drawing canvas
A structural view of the draw class is given in slide [draw-structure].
The draw class is derived from a canvas, which is itself
(indirectly) derived from a handler class.
The handler class dispatches events to predefined
handler methods, such as press, motion and release.
For the draw class we must distinguish between
a handler and a canvas part.
The handler part is defined by the methods
press, release and motion.
The canvas part allows for drawing figures, such as a small circle.
slide: Drawing canvas
In slide [draw-interact] it is depicted how these
two parts interact when the user draws a figure.
Actions of the user result in events that activate
the handler.
Note that the UML sequence diagrams are not completely
adequate here, since it is difficult to express
information concerning the events and the state of the draw
instance.
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 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 Return,
which is the default event for
the entry widget.
The function may be
used to associate a handler object
with the default bindings
for the widget.
Concrete widgets may not override the bind
function itself, but must define the protected
function install.
Typically, the install function consists
of calls to bind
for each of the event types that is
relevant to the widget.
In addition, the widget class offers two
functions that may be used
when defining compound or mega widgets.
The function must by used
to delegate the invocation
of the eval, configure and bind functions
to the widget w.
The function gives access to the widget
to which the commands are redirected.
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 function install must be redefined
to handle the delegation explicitly.
The drawtool application
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;
}
};
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");
}
};
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 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);
}
};
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);
}
};
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 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 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
}
};
Defining a widget command involves three steps: (I)
the declaration of the binding between
a command and a handler,
(II) the definition of the
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
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 , 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();
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.
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.