/*
*-------------------------------------------------------------- 80 columns ---|
* The RectShape class defines a simple rectangular shape object.
* It tracks its bounding box, selected state, and the canvas it is being
* drawn in. It has some basic methods to select, move, and resize the
* rectangle. It has methods that draw the shape in the selected or unselected
* states and updates the canvas whenever the state or bounds of the rectangle
* change. The code that is there works properly, but you will need to extend
* and change the code to support additional features.
*
* @version 1.0 10/13/99
* @author Julie Zelenski
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
// this import needed on mac os 9x
// import com.sun.java.util.collections.*;
public class DrawingCanvas extends JPanel
{
public static void main(String [] args)
{ new DrawingCanvas();
}
/**
* Constructor for creating a new empty DrawingCanvas. We set up
* our size and background colors, instantiate an empty vector of shapes,
* and install a listener for mouse events using our inner class
* CanvasMouseHandler
*/
public DrawingCanvas(Toolbar tb, int width, int height)
{
setPreferredSize(new Dimension(width, height));
setBackground(Color.white);
toolbar = tb;
allShapes = new ArrayList();
selectedShape = null;
CanvasMouseHandler handler = new CanvasMouseHandler();
addMouseListener(handler);
addMouseMotionListener(handler);
}
/**
* All components are responsible for drawing themselves in
* response to repaint() requests. The standard method a component
* overrides is paint(Graphics g), but for Swing components, the default
* paint() handler calls paintBorder(), paintComponent() and paintChildren()
* For a Swing component, you override paintComponent and do your
* drawing in that method. For the drawing canvas, we want to
* clear the background, then iterate through our shapes asking each
* to draw. The Graphics object is clipped to the region to update
* and we use to that avoid needlessly redrawing shapes outside the
* update region.
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Rectangle clipRect = g.getClipBounds();
Iterator iter = allShapes.iterator();
while (iter.hasNext()) {
((Rect)iter.next()).draw(g, clipRect);
}
}
/**
* Changes the currently selected shape. There is at most
* one shape selected at a time on the canvas. It is possible
* for the selected shape to be null. Messages the shape to
* change its selected state which will in turn refresh the
* shape with the knobs active.
*/
protected void setSelectedShape(Rect shapeToSelect)
{
if (selectedShape != shapeToSelect) { // if change in selection
if (selectedShape != null) // deselect previous selection
selectedShape.setSelected(false);
selectedShape = shapeToSelect; // set selection to new shape
if (selectedShape != null) {
shapeToSelect.setSelected(true);
}
}
}
/**
* A hit-test routine which finds the topmost shape underneath a
* given point.We search Vector of shapes in back-to-front order
* since shapes created later are added to end and drawn last, thus
* appearing to be "on top" of the earlier ones. When a click comes
* in, we want to select the top-most shape.
*/
protected Rect shapeContainingPoint(Point pt)
{
for (int i = allShapes.size()-1; i >= 0; i--)
{
Rect r = (Rect)allShapes.get(i);
if (r.inside(pt)) return r;
}
return null;
}
/*
* The inner class CanvasMouseHandler is the object that handles the
* mouse actions (press, drag, release) over the canvas. Since there is
* a bit of state to drag during the various operations (which shape,
* where we started from, etc.) it is convenient to encapsulate all that
* state with this little convenience object and register it as the
* handler for mouse events on the canvas.
*/
protected class CanvasMouseHandler extends MouseAdapter implements MouseMotionListener
{
Point dragAnchor; // variables using to track state during drag operations
int dragStatus;
/** When the mouse is pressed we need to figure out what
* action to take. If the tool mode is arrow, the click might
* be a select, move or reisze. If the tool mode is one of the
* shapes, the click initiates creation of a new shape.
*/
public void mousePressed(MouseEvent event)
{
Rect clicked = null;
Point curPt = event.getPoint();
if (toolbar.getCurrentTool() == Toolbar.SELECT) {
// first, determine if click was on resize knob of selected shape
if (selectedShape != null && (dragAnchor = selectedShape.getAnchorForResize(curPt)) != null) {
dragStatus = DRAG_RESIZE; // drag will resize this shape
} else if ((clicked = shapeContainingPoint(curPt)) != null) { // if not, check if any shape was clicked
setSelectedShape(clicked);
dragStatus = DRAG_MOVE; // drag will move this shape
dragAnchor = curPt;
} else { // else this was a click in empty area, deselect selected shape,
setSelectedShape(null);
dragStatus = DRAG_NONE; // drag does nothing in this case
}
} else {
Rect newShape = new Rect(curPt, DrawingCanvas.this); // create rect here
allShapes.add(newShape);
setSelectedShape(newShape);
dragStatus = DRAG_CREATE; // drag will create (resize) this shape
dragAnchor = curPt;
}
}
/** As the mouse is dragged, our listener will receive periodic
* updates as mouseDragged events. When we get an update position,
* we update the move/resize event that is in progress.
*/
public void mouseDragged(MouseEvent event)
{
Point curPt = event.getPoint();
switch (dragStatus) {
case DRAG_MOVE:
selectedShape.translate(curPt.x - dragAnchor.x, curPt.y - dragAnchor.y);
dragAnchor = curPt; // update for next dragged event
break;
case DRAG_CREATE: case DRAG_RESIZE:
selectedShape.resize(dragAnchor, curPt);
break;
}
}
public void mouseMoved(MouseEvent e) {}
static final int DRAG_NONE = 0, DRAG_CREATE = 1, DRAG_RESIZE = 2, DRAG_MOVE = 3;
}
/** A little helper routine that will be useful for the load & save
* operations. It brings up the standard JFileChooser dialog and
* allows the user to specify a file to open or save. The return
* value is the full path to the chosen file or null if no file was
* selected.
*/
protected String filenameChosenByUser(boolean forOpen)
{
JFileChooser fc = new JFileChooser(System.getProperty("user.dir") + java.io.File.separator + "Documents");
int result = (forOpen? (fc.showOpenDialog(this)) : fc.showSaveDialog(this));
java.io.File chosenFile = fc.getSelectedFile();
if (result == JFileChooser.APPROVE_OPTION && chosenFile != null)
return chosenFile.getPath();
return null; // return null if no file chosen or dialog cancelled
}
/* These are the unimplemented menu commands. The menus are already
* set up to send the correct messages to the canvas, but the
* method bodies themselves are currently completely empty. It will
* be your job to fill them in!
*/
public void cut() {}
public void copy() {}
public void paste() {}
public void delete() {}
public void clearAll() {}
public void loadFile() {}
public void saveToFile() {}
public void bringToFront() {}
public void sendToBack() {}
protected ArrayList allShapes; // list of all shapes on canvas
protected Rect selectedShape; // currently selected shape (can be null at times)
protected Toolbar toolbar; // reference to toolbar to message for tool&color settings
}