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 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
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
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 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 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 must by used
to delegate the invocation
of the eval, configure, bind and handler functions
to the widget w.
The function 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 .
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 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 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 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 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 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
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
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 is invoked
for , 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
, or
implicitly by using .
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 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.