In the Software Engineering curriculum at the Vrije Universiteit, we have repeatedly used interactive editors as a medium-term assignment for CS2 students (five weeks for groups of four or five students). One example of such an assignment is the Interactive Design Assistant discussed in section IDA. Another example is the musical score editor (see appendix Projects), which has been chosen by a selected group of CS3 and CS4 students as a practical assignment for the Object-Oriented Software Development course.
In this section we will look at the drawtool application, which is a representative realization of a (rather simple) drawing editor. The implementation of drawtool presented here is realized in the Java version of the hush framework. The hush C++ framework has been used for a number of years in the Software Engineering curriculum, but has recently been replaced by Java with Swing. The drawtool application is nevertheless interesting because it acted for many years as the basic example of an interactive editor for quite a number of students.
Before studying drawtool, we will first look at
the realization of a drawing canvas in hush
A simple drawing canvas in hush
In the widget class hierarchy depicted on the right in slide hush-overview, 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 references to widgets defined in Tcl. In contrast, employing the constructor of one of the concrete widget classes results in actually creating a widget.
As an example look at the code for the drawing canvas
widget depicted in slide drawing-canvas.
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.
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.
A structural view of the drawtool application is given
in slide drawtool-structure.
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.
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.
draft version 0.1 (15/7/2001)
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 drawtool application
The toolbox component
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;
}
};
public toolbutton(widget w, String name) {
super(w,name);
text(name);
bind(w,name);
pack("-side top -fill both -expand 1");
}
};
The menubar component
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 tablet component
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 drawtool class
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
}
};
[]
readme
course
preface
1
2
3
4
5
6
7
8
9
10
11
12
appendix
lectures
resources
eliens@cs.vu.nl