In this tutorial we develop a figure that draws a straight line. You can find the source code of the StraightLineFigure class in the package org.jhotdraw.samples.mini.
This tutorial consist of the following steps:
Step 1: Check for interfaces
Step 2: Check for existing code
Step 3: Write a skeleton class
Step 4: Implement the draw methods
Step 5: Implement the bounds methods
Step 6: Implement the transform methods
Step 7: Implement the contains method
Step 8: Implement the clone method
Before you start creating a new Figure class for the JHotDraw framework, you should take a look at the ‹All Known Subinterfaces› list and the ‹All Known Implementing Classes› list in the JavaDoc documentation of the Figure interface.
In the ‹All Known Subinterfaces› list you find interfaces that cover specialized feature sets of Figure objects.
For this tutorial, we don't need to use any of these specialized interfaces, because we just want to draw a straight line.
In the ‹All Known Implementing Classes› list of the Figure interface you find abstract classes that you can use as building blocks for your own figure class.
Maybe you find a default implementation of the Figure interface here, that saves you the time from writing your own figure from scratch. For example, there is no need for you to implement a RectangleFigure by yourself, because this class is already there. at least, you may find sample implementations of the Figure interface, that you can use as a copy and paste source for your own figure.
If it weren't for this tutorial, we could just use the existing LineFigure class and go all home now. But today, we wan't to do it the hard way, so…
…lets take a look at the abstract classes that the JHotDraw framework provides:
For our StraightLineFigure, we will subclass from AbstractAttributedFigure.
An IDE can generate all the empty methods for us. All we need to do, is to choose the class we want to extend from. Here is the skeleton code we get from NetBeans, when creating an empty class named StraightLineFigure that extends from AbstractAttributedFigure:
import java.awt.*;
import java.awt.geom.*;
import org.jhotdraw.draw.*;
public class StraightLineFigure extends AbstractAttributedFigure {
protected void drawFill(Graphics2D g) {}
protected void drawStroke(Graphics2D g) {}
public void basicSetBounds(Point2D.Double start, Point2D.Double end) {} public Rectangle2D.Double getBounds() {}
public void basicTransform(AffineTransform ty) {}
public Object getTransformRestoreData() { return null; }
public void restoreTransformTo(Object restoreData) {}
public boolean contains(Point2D.Double p) { return false; }
}
The draw methods are used to render a Figure onto a Graphics2D object. The Graphics2D object serves as the canvas for the Figure.
Since we decided to subclass from AbstractAttributedFigure in Step 2, we do not need to configure the Graphics2D object. AbstractAttributedFigure applies all attributes of the Figure to the Graphics2D object before it invokes the drawFill and the drawStroke methods. Thus we do not need to write any code that sets the color, stroke or paint of the Graphics2D object.
In order to draw a straight line, we can use a Line2D.Double object from the java.awt.geom package.
line
that holds the
line object.Here is the resulting code:
public class StraightLineFigure extends AbstractAttributedFigure { private Line2D.Double line;
public StraightLineFigure() {
line = new Line2D.Double();
} protected void drawFill(Graphics2D g) { // empty }
protected void drawStroke(Graphics2D g) { g.draw(line); } …
}
The bounds methods are used by Tool objects to manipulate the Figure. They also allow to programatically change the bounds of a Figure.
public class StraightLineFigure extends AbstractAttributedFigure { private Line2D.Double line;
public void basicSetBounds(Point2D.Double start, Point2D.Double end) { line.setLine(start, end);
}
public Rectangle2D.Double getBounds() {
return (Rectangle2D.Double) line.getBounds2D();
} …
}
The transform methods are used by some Handle objects to manipulate the Figure.
The basicTransform method performs the transformation, by transforming the two end points of the line object using the passed AffineTransform object from the java.awt API.
The getTransformRestoreData and restoreTransformTo methods are used to provide (near) lossless transformation and to provide undo functionality. Before a Handle transforms the Figure the first time, it retrieves an object from method getTransformRestoreData. Method restoreTransformTo is used by the Handle to restore the original transformation of the Figure whenever this is needed.
In our implementation we return a clone of the line in method getTranformRestoreData. In method restoreTransformDataTo, we set the line to yet another clone of the restored data.
public class StraightLineFigure extends AbstractAttributedFigure { private Line2D.Double line;
public void basicTransform(AffineTransform ty) {
Point2D.Double p1 = (Point2D.Double) line.getP1();
Point2D.Double p2 = (Point2D.Double) line.getP2();
line.setLine(
ty.transform(p1, p1),
ty.transform(p2, p2)
);
}
public Object getTransformRestoreData() {
return line.clone();
}
public void restoreTransformTo(Object restoreData) {
line = (Line2D.Double) ((Line2D.Double) restoreData).clone();
} …
}
The contains method is used by Tool objects to perform hit tests on the Figure, for example to determine on which Figure a mouse click was performed.
Altough, class Line2D.Double already provides a contains method, we use the contains method of the org.jhotdraw.geom.Geom class. This method allows us to pass in the total width of the line using the tolerance parameter.
public class StraightLineFigure extends AbstractAttributedFigure { private Line2D.Double line;
public boolean contains(Point2D.Double p) {
return Geom.lineContainsPoint(
line.x1,line.y1,
line.x2, line.y2,
p.x, p.y,
AttributeKeys.getStrokeTotalWidth(this));
}
…
}
The JHotDraw framework uses cloning for creating new objects. It is very important that the clone method of a Figure is properly implemented, or we will encounter problems, if more of one StraigthLineFigure object is in a Drawing.
public class StraightLineFigure extends AbstractAttributedFigure { private Line2D.Double line;
public StraightLineFigure clone() {
StraightLineFigure that = (StraightLineFigure) super.clone();
that.line = (Line2D.Double) this.line.clone();
return that;
} …
}