How-to Java, by Todd Sundsted

Examining HotSpot, an object-oriented drawing program

Learn how the pieces of the Java language and class library fit together through a study of this Java program

Summary
This column is about HotSpot -- a program that demonstrates what can be accomplished in Java using only a few tools. In this case, our tools are the Graphics class, the Event class, the Observable class, the Observer interface, and the Component and Container classes that are supplied with the abstract windowing toolkit. This column describes these classes and shows how they can be combined to create a "real" program. (2,200 words)

After spending several months looking at the parts of a system one piece at a time in past "How To Java" columns, it's time to learn how to put them together and actually build something. The result is HotSpot.

This column builds on several subjects covered in prior columns -- including graphics, observers and observables, events, and components and containers.

The application
HotSpot is a simple, object-oriented drawing program. A drawing program allows a user to create a picture from a palette of simple geometric shapes. Once a shape has been placed on the drawing surface, the user can reselect it and adjust its size, shape, and position. In this respect, a drawing program differs from a paint program, in which a shape, once placed on the drawing surface, becomes nothing more than a colored collection of pixels.

Figure 1 shows a typical HotSpot drawing surface or "canvas." This canvas holds a single shape -- a square -- which has small gray boxes at its corners and center. These boxes are called hot spots, and they allow the user to manipulate the square.

Figure 1: A HotSpot canvas

Figure 2 illustrates the role hot spots play. The user selects a specific shape on the canvas by positioning the mouse over one of the hot spots and clicking on it. The user can then adjust the shape, size, and position of the selection by dragging the hot spot with the mouse.

Figure 2: Interaction with a HotSpot shape

The program HotSpot was named, obviously, after these hot spots. In keeping with the principles of object-oriented software design, each of the major components of the HotSpot program is represented by a Java language class. This set of components includes the canvas, the shapes, and even the hot spots.

Table 1 lists the classes that make up the HotSpot program. Class Shape and its subclasses represent the types of shapes that can be placed on a canvas. A Shape object contains a list of HotSpot objects that represent that shape's hot spots. Shape objects are managed by a ShapeMgr object, which is nothing more than a fancy subclass of class Canvas. The ShapeMgr object plays two roles: It manages a list of shapes and provides a place for them to draw themselves. The ShapeMgr object contains a HotSpotMgr object. In a manner similar to that of the ShapeMgr object, the HotSpotMgr object manages a list of HotSpot objects and also provides a convenient mechanism for locating the hot spot at a particular set of coordinates.

Shape public abstract class Shape implements Observer
RectangleShape public class RectangleShape extends Shape implements Observer
OvalShape public class OvalShape extends Shape implements Observer
LineShape public class LineShape extends Shape implements Observer
HotSpot public class HotSpot extends Observable
ShapeMgr public class ShapeMgr extends Canvas
HotSpotMgr public class HotSpotMgr
Main public class Main extends Applet
Dimensions public class Dimensions
Coordinates public class Coordinates

The pieces
By now two things should be obvious. First, HotSpot is not a particularly revolutionary drawing program. Second, a lot of thought was put into the design of HotSpot to ensure that it effectively demonstrates the material introduced in prior "How To Java" columns.

Table 2 illustrates the relationship between the classes in Table 1 and the subjects mentioned in the introduction. By structuring this article in this fashion, I hope to associate the subject matter of my earlier columns with these classes, thereby providing a concrete, relevant example of their place in the context of a "real" program.

graphics classes Shape and HotSpot
observers and observables classes Shape, HotSpot, and ShapeMgr
events class ShapeMgr
components and containers classes ShapeMgr and Main

Graphics
In addition to providing the internal representation of hot spots and shapes, class HotSpot and class Shape must provide for their graphical representation on a canvas. Internally, instances of class HotSpot and class Shape contain information about their respective positions. The classes provide a paint() method that uses this information to draw the graphical representation on a suitable surface. The code for the paint() method of class HotSpot is:

public void paint(Graphics g)
{
   g.fillRect(_coord.x - _dim.w / 2,
              _coord.y - _dim.h / 2,
              _dim.w - 1,
              _dim.h - 1);
}

The code for class RectangleShape's paint() method is:

public void paint(Graphics g)
{
   int x = _w < 0 ? _x + _w : _x;
   int y = _h < 0 ? _y + _h : _y;
   int w = _w < 0 ? -_w : _w;
   int h = _h < 0 ? -_h : _h;

   g.drawRect(x, y, w, h);
}

Since class Shape is abstract, class RectangleShape was selected as a representative non-abstract subclass of class Shape. Both methods require, as a parameter, an instance of class Graphics. The paint() method for class HotSpot uses the Graphics object's fillRect() method to draw a small rectangle on the canvas. The paint() method of class RectangleShape first ensures that the shape's width and height parameters are non-negative, and then calls the Graphics object's drawRect() method to draw a rectangle of the correct size. Each of class Shape's subclasses also provides a paint() method that functions like that of class RectangleShape's paint() method.

Observers and Observables
Consider the user's interaction with the drawing program once more. As the user changes the location of a hot spot with the mouse, both the position and size of the associated shape and the position of some of the shape's other hot spots change. Clearly the program needs some mechanism for passing information about one hot spot's change in position to the shape and to the other hot spots whose positions are dependent in some way on its position. Class Observable and interface Observer allow HotSpot to solve this problem. The following is the code for Class RectangleShape's update() method:

public void update(Observable obs, Object obj)
{
   Coordinates coordTL = _hsTL.getCoordinates();
   Coordinates coordTR = _hsTR.getCoordinates();
   Coordinates coordBL = _hsBL.getCoordinates();
   Coordinates coordBR = _hsBR.getCoordinates();
   Coordinates coordC = _hsC.getCoordinates();

   if (obs == _hsTL)
   {
      _x = coordTL.x;
      _y = coordTL.y;
      _w = coordBR.x - coordTL.x;
      _h = coordBR.y - coordTL.y;
   }
   else if (obs == _hsTR)
   {
      _y = coordTR.y;
      _w = coordTR.x - coordTL.x;
      _h = coordBR.y - coordTR.y;
   }
   else if (obs == _hsBL)
   {
      _x = coordBL.x;
      _w = coordBR.x - coordBL.x;
      _h = coordBL.y - coordTL.y;
   }
   else if (obs == _hsBR)
   {
      _w = coordBR.x - coordTL.x;
      _h = coordBR.y - coordTL.y;
   }
   else if (obs == _hsC)
   {
      _x = coordC.x - _w/2;
      _y = coordC.y - _h/2;
   }

   _hsTL.setCoordinates(new Coordinates(_x, _y));
   _hsTR.setCoordinates(new Coordinates(_x + _w, _y));
   _hsBL.setCoordinates(new Coordinates(_x, _y + _h));
   _hsBR.setCoordinates(new Coordinates(_x + _w, _y + _h));
   _hsC.setCoordinates(new Coordinates(_x + _w/2, _y + _h/2));
}

Each of the subclasses of class Shape implements the Observer interface, which means that each of these classes must implement an update() method that takes as a parameter an instance of class Observable. The code above shows how this method was implemented for class RectangleShape. The update() method determines the coordinates of each of the shape's hot spots and recalculates all of their positions based on the hot spot whose position changed.

When an instance of class RectangleShape is created, it creates a set of HotSpot instances. These instances represent the places on the shape where the shape can be manipulated by the user. By calling each instance's addObserver() method, the rectangle shape notifies each instance that it wants to be informed when the hot spot changes state. The following code, representing Class HotSpot's Set methods, shows how this occurs in the constructor for class RectangleShape.

public RectangleShape(int x, int y, int w, int h)
{
   _x = x;
   _y = y;
   _w = w;
   _h = h;

   _hsTL = new HotSpot(new Coordinates(x, y));
   _hsTR = new HotSpot(new Coordinates(x + w, y));
   _hsBL = new HotSpot(new Coordinates(x, y + h));
   _hsBR = new HotSpot(new Coordinates(x + w, y + h));
   _hsC = new HotSpot(new Coordinates(x + w/2, y + h/2),
                      new Dimensions(11, 11));

   _hsTL.addObserver(this);
   _hsTR.addObserver(this);
   _hsBL.addObserver(this);
   _hsBR.addObserver(this);
   _hsC.addObserver(this);
}
public void setCoordinates(Coordinates coord)
{
   _coord = new Coordinates(coord);

   setChanged();
}

public void setDimensions(Dimensions dim)
{
   _dim = new Dimensions(dim);

   setChanged();
}

It should be obvious that class HotSpot must be a subclass of class Observable. This means that other classes have access to methods by which they can announce their interest in the state of a hot spot. The HotSpot itself has only to remember to call its setChanged() method when it changes state. The code above shows the two methods by which a HotSpot object's dimensions and coordinates are set. In both cases the instance calls its setChanged() method in order to set the internal state flag.

Class ShapeMgr also plays a role in the notification process. It is neither an observer nor an observable, however it does call a HotSpot object's notifyObservers() method immediately after setting the object's coordinates due to mouse drag. The following code shows Class ShapeMgr's mouseDrag() method. The invocation of the HotSpot object's notifyObservers() method results in the appropriate shape being notified of the change in the hot spot's location.

public boolean mouseDrag(Event e, int x, int y)
{
   if (_hs != null)
   {
      _hs.setCoordinates(new Coordinates(x, y));
      _hs.notifyObservers();

      repaint();
   }

   return true;
}

Events
The activity within a typical AWT-based program is directed by events -- and HotSpot is no exception. It is interested in three related events: mouse-down events, mouse-up events, and mouse-drag events. It is by the creation and propagation of these three events that a hot spot (and its associated shape) is selected and repositioned.

Class ShapeMgr is a subclass of class Canvas and is therefore eligible to receive user-interface events. It handles the mouse-down, mouse-up, and mouse-drag events by replacing the default mouseDown(), mouseUp(), and mouseDrag() methods provided by class Component with its own specialized event handling code. The code below shows these three methods as implemented by class ShapeMgr. The mouseDown() event handler determines whether or not there is a hot spot at the position on the canvas where the mouse touches down. If a hot spot is present, the mouseDown() event handler saves a reference to that hot spot for future use. The mouseUp() event handler releases this reference. The mouseDrag() event handler is called whenever the mouse moves while the mouse button is down. It is therefore called repeatedly after a mouseDown() event but only before the corresponding mouseUp() event. Every time it is called, it updates the coordinates of the selected hot spot and notifies all observers that the hot spot's position has changed.

public boolean mouseDrag(Event e, int x, int y)
{
   if (_hs != null)
   {
      _hs.setCoordinates(new Coordinates(x, y));
      _hs.notifyObservers();

      repaint();
   }

   return true;
}

public boolean mouseDown(Event e, int x, int y)
{
   _hs = _hsm.isAt(new Coordinates(x, y));

   return true;
}

public boolean mouseUp(Event e, int x, int y)
{
   _hs = null;

   return true;
}

Components and containers
The components and containers provided by the AWT form the foundation of our program. The program itself is contained entirely within an instance of the Panel class. A subclass of the Canvas class provides the drawing surface. Instances of class Button allow the user to drop shapes onto the canvas.

The following program is perhaps the most compelling evidence of the importance of these basic components and containers because it contains the HotSpot applet itself. It simply could not exist without the framework provided by the AWT.

You need a Java-enabled browser to view this applet.

Conclusion
HotSpot, as it currently exists, is far from complete. It needs the additional shapes, colors, and fills, as well as rotation, grouping, and a host of related functions that are almost always present in commercial drawing programs. From the standpoint of its appearance, double-buffering would reduce the flickering that occurs when resizing or repositioning a shape. However, even without these extras, HotSpot is (I hope) a reasonable example of a "full-fledged" Java program. Let me know what you think.

Next month I plan to finish my coverage of the Graphics class. Still unexplored are two of the Graphics class' more interesting elements -- text and images. As always, I'm interested in hearing your ideas. Write me and let me know what you'd like to see covered down the road. []

About the author
Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing. Todd is co-author of the Java Language API SuperBible, soon to appear in bookstores everywhere. In addition to writing, Todd provides Internet and Web consulting services to companies in the southeastern United States. You can reach him at todd.sundsted@javaworld.com

Resources

[(c) Copyright 1996 Web Publishing Inc., an IDG Communications company]

Original URL: http://www.javaworld.com/javaworld/jw-12-1996/jw-12-howto.html
Last updated: 15 November 1996