[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 7.4


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 7.4:

In Exercise 6.1, you wrote a program SimpleStamperWithDrag that allows the user to place red rectangles and blue ovals in a panel by clicking and dragging the mouse. However, that program does not store any information about what has been drawn, so the panel cannot repaint itself correctly. Revise the program to use an ArrayList to store data about the contents of the panel. All drawing should be done in a paintComponent() method.


Discussion

We need three pieces of information in order to draw a shape: its x-coordinate, its y-coordinate, and whether it is a red rectangle or a blue oval. We can pack the three pieces of information into a class. I've added a constructor to the class to make it easier to create an object and set the values of its instance variables at the same time:

/**
 * A class to hold information about one shape.
 */
private static class ShapeInfo {
    int x,y;  // The location of a shape
    boolean rect;  // true if it's a red rectangle, false for a blue oval
    ShapeInfo(int x, int y, boolean isRect) {
        this.x = x;
        this.y = y;
        rect = isRect;
    }
}

The data for the whole collection of shapes is then stored using a variable named shapes of type ArrayList<ShapeInfo>. By using an ArrayList, we allow for an unlimited number of shapes.

Given this data structure, it's easy to write the paintComponent() method. It uses a for-each loop to go through the ArrayList. The loop control variable, shape, is of type ShapeInfo, and the data needed to draw the shape is given by its three instance variables shape.x, shape.y, and shapes.rect. You can look at the method in the program below.

In the original program, the mousePressed() and mouseDragged() methods actually draw the shapes. In the new version, these methods simply add an item to the shapes ArrayList, and they call repaint() to make the change show up on the screen. (I will admit that in my first attempt, I forgot the repaint(), with the result that the mouse actions had no visible effect even though the shape was correctly added to the list.)

If the user is holding down the shift key in mousePressed(), the panel has to be cleared. I do that by calling shapes.clear(), which removes all the items from the ArrayList and sets its size to zero.

(By the way, one of the reasons to use an ArrayList in this program is to avoid avoid using the getGraphics() method to get a graphics context for drawing on the panel. The use of this method is frowned upon. The new program follows the recommended rule, to do all drawing in the paintComponent() method.)


The Solution

Changes from SimpleStamperWithDrag are shown in red.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.ArrayList;

/**
 * A simple demonstration of MouseEvents.  Shapes are drawn
 * on a black background when the user clicks the panel.  If
 * the user Shift-clicks, the panel is cleared.  If the user
 * right-clicks the panel, a blue oval is drawn.  Otherwise,
 * when the user clicks, a red rectangle is drawn.  Information
 * about the shapes that have been drawn is stored in an ArrayList,
 * which is used in paintComponent() to draw the contents of
 * the panel.
 */
public class SimpleStamperWithArrayList extends JPanel 
                               implements MouseListener, MouseMotionListener {

    public static void main(String[] args) {
        JFrame window = new JFrame("Simple Stamper");
        SimpleStamperWithArrayList content = new SimpleStamperWithArrayList();
        window.setContentPane(content);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setLocation(120,70);
        window.setSize(400,300);
        window.setVisible(true);
    }

    // ----------------------------------------------------------------------
    
    /**
     * A class to hold information about one shape.
     */
    private static class ShapeInfo {
        int x,y;  // The location of a shape
        boolean rect;  // true if it's a red rectangle, false for a blue oval
        ShapeInfo(int x, int y, boolean isRect) {
            this.x = x;
            this.y = y;
            rect = isRect;
        }
    }
    
    private ArrayList<ShapeInfo> shapes;  // Holds shapes that have been added.

    /**
     * This variable is set to true during a drag operation, unless the
     * user was holding down the shift key when the mouse was first
     * pressed (since in that case, the mouse gesture simply clears the
     * panel and no figures should be drawn if the user drags the mouse).
     */
    private boolean dragging;
    

    /**
     * This constructor simply sets the background color of the panel to be black
     * and sets the panel to listen for mouse events on itself.
     */
    public SimpleStamperWithArrayList() {
        setBackground(Color.WHITE);
        addMouseListener(this);
        addMouseMotionListener(this);
        shapes = new ArrayList<ShapeInfo>();
    }


    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for ( ShapeInfo shape : shapes ) {
            if ( shape.rect ) {
                    // Draw a red rectangle centered at (x,y).
                g.setColor(Color.RED);   // Red interior.
                g.fillRect( shape.x - 30, shape.y - 15, 60, 30 );
                g.setColor(Color.BLACK); // Black outline.
                g.drawRect( shape.x - 30, shape.y - 15, 60, 30 );
            }
            else {
                    // draw a blue oval centered at (x,y)
                g.setColor(Color.BLUE);  // Blue interior.
                g.fillOval( shape.x - 30, shape.y - 15, 60, 30 );
                g.setColor(Color.BLACK); // Black outline.
                g.drawOval( shape.x - 30, shape.y - 15, 60, 30 );
            }
        }
    }
    
    /**
     *  This method will be called when the user clicks the mouse on the panel.
     *  If the shift key is down, the panel is cleared.  Otherwise, it adds
     *  a shape to the panel and starts a drag operation.
     */
    public void mousePressed(MouseEvent evt) {

        if ( evt.isShiftDown() ) {
                // The user was holding down the Shift key.
                // Set shapeCount to 0 to record that there are no shapes.
            dragging = false;
            shapes.clear();
            repaint();
            return;
        }

        dragging = true;

        int x = evt.getX();  // x-coordinate where user clicked.
        int y = evt.getY();  // y-coordinate where user clicked.

        ShapeInfo newShape;
        if ( evt.isMetaDown() ) {
            newShape = new ShapeInfo(x,y,false);
        }
        else {
            newShape = new ShapeInfo(x,y,true);
        }
        shapes.add(newShape);
        repaint();  // repaint so that user can see the new shape!

    } // end mousePressed();


    /**
     *  This method is called when the user drags the mouse.  If a the value of the
     *  instance variable dragging is true, it will add a shape to the panel.
     */
    public void mouseDragged(MouseEvent evt) {
        if ( dragging == false ) { 
            return;
        }
        int x = evt.getX();  // x-coordinate where user clicked.
        int y = evt.getY();  // y-coordinate where user clicked.

        ShapeInfo newShape;
        if ( evt.isMetaDown() ) {
            newShape = new ShapeInfo(x,y,false);
        }
        else {
            newShape = new ShapeInfo(x,y,true);
        }
        shapes.add(newShape);

        repaint();  // repaint so that user can see the new shape!
    } // end mouseDragged();


    // The next four empty routines are required by the MouseListener interface.
    // They don't do anything in this class, so their definitions are empty.

    public void mouseEntered(MouseEvent evt) { }
    public void mouseExited(MouseEvent evt) { }
    public void mouseClicked(MouseEvent evt) { }
    public void mouseReleased(MouseEvent evt) { }

    // The next routines is required by the MouseMotionListener interface.

    public void mouseMoved(MouseEvent evt) { }

} // end class SimpleStamperWithArrayList

[ Exercises | Chapter Index | Main Index ]