[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 6.1


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


Exercise 6.1:

In the SimpleStamper example from Subsection 6.3.3, a rectangle or oval is drawn on the panel when the user clicks the mouse. Except, when the user shift-clicks, the panel is cleared instead. Modify this class so that the modified version will continue to draw figures as the user drags the mouse. That is, the mouse will leave a trail of figures as the user drags. However, if the user shift-clicks, the panel should simply be cleared and no figures should be drawn even if the user drags the mouse after shift-clicking. Here is a picture of my solution:

the programming showing trails of ovals and rects

The source code for the original program is SimpleStamper.java. See the discussion of dragging in Subsection 6.3.4. (Note that the original version uses a black background, with a black border around each shape. That didn't work well with a lot of closely spaced shapes, so the new version uses a white background.)

If you want to make the problem a little more challenging, when drawing shapes during a drag operation, make sure that the shapes that are drawn are at least, say, 5 pixels apart. To implement this, you have to keep track of the position of the last shape that was drawn.


Discussion

In order to implement dragging in the new version, we need a MouseMotionListener in addition to the MouseListener that is already present in the original version. In the original, the panel class itself implements MouseListener, so I just added MouseMotionListener:

public class SimpleStamperWithDrag extends JPanel 
                          implements MouseListener, MouseMotionListener { . . .

Of course, the mouse motion listener has to be registered with the panel in order for it to hear any events from the panel. This is done by adding the line

addMouseMotionListener(this);

to the constructor. Here "this" refers to the panel object itself and is used because the panel itself implements MouseMotionListener, and it will listen for mouse motion events from itself. (It's awfully easy to forget this step when implementing event-handling! Nothing will happen when the event occurs, and it can be hard to realize what the problem is.)

To finish the implementation of the mouse motion listener interface, the mouseMoved and mouseDragged methods must be added to the class. The program does not respond when the user moves the mouse without holding down any mouse button, so the mouseMoved method is empty. The mouseDragged method must draw a figure at the current mouse position; the code for this is almost identical to the existing drawing code in the mousePressed routine and can be copied from there. However, nothing should be drawn in the mouseDragged method if the user started the mouse drag gesture by shift-clicking. The discussion of dragging in Subsection 6.3.4, suggests that the program should use an instance variable named dragging to keep track of whether or not to draw anything in the mouseDragged method. In the mousePressed routine, this variable is set to false if the user shift-clicked, and to true otherwise. The mouseDragged routine checks the value of dragging; if the value is false, it means that the drag started with a shift-click and therefore nothing should be drawn. The complete source code is shown below.

The mouseDragged() method also needs to know whether the user clicked with the right mouse button. That is something that is determined in mousePressed(). To get the information from the mousePressed() to the mouseDragged() method, I added another instance variable, rightMouse, of type boolean. Its value is set to true in mousePressed() if the right mouse button was pressed and to false if not. Its value is checked in mouseDragged() to decide whether to draw a blue oval or a red rectangle.


The picture produced by the program would look better if there were always at least a few pixels between the shapes that are drawn as the user drags the mouse, as suggested at the end of the exercise. It is not difficult to make the change. The panel needs two new instance variables, prevX and prevY, of type int, to store the position of the shape that was drawn most recently. Their values should be set after drawing a shape in both mousePressed() and mouseDragged() with the statements

prevX = x;
prevY = y;

The values of prevX and prevY can then be tested at the beginning of the mouseDragged() method to decide whether or not to draw a shape. The shape should be drawn only if either the x-coordinate or the y-coordinate has changed by at least 5 pixels since the last time a shape was drawn. For example, mouseDragged() could make the test as follows:

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.
      if ( Math.abs( prevX - x ) < 5 && Math.abs( prevY - y) < 5 )
         return;
      .
      .
      .

The Solution

Here is the code for the modified panel class, with changes from the original (SimpleStamper.java) shown in red:

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

/**
 * 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.  The contents of
 * the panel are not persistent.  For example, they might disappear 
 * if the panel is resized or is covered and uncovered.
 * Ovals and rects continue to be drawn as the user drags the mouse.
 * This class has a main() routine to allow it to be run as an application.
 */
public class SimpleStamperWithDrag extends JPanel 
                               implements MouseListener, MouseMotionListener {

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

    // ----------------------------------------------------------------------

    /**
     * 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 variable is set to true if the user clicks with the right
     * mouse button, and therefore is drawing blue ovals.  It is tested
     * in the mouseDragged() method to decide what to draw.
     */
    private boolean rightMouse;
    

    /**
     * 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 SimpleStamperWithDrag() {
        setBackground(Color.WHITE);
        addMouseListener(this);
        addMouseMotionListener(this);
    }


    /**
     *  Since this panel has been set to listen for mouse events on itself, 
     *  this method will be called when the user clicks the mouse on the panel.
     *  This method is part of the MouseListener interface.
     */
    public void mousePressed(MouseEvent evt) {

        if ( evt.isShiftDown() ) {
                // The user was holding down the Shift key.  Just repaint the panel.
                // Since this class does not define a paintComponent() method, the 
                // method from the superclass, JPanel, is called.  That method simply
                // fills the panel with its background color, which is black.  The 
                // effect is to clear the panel.
            dragging = false;
            repaint();
            return;
        }

        dragging = true;

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

        Graphics g = getGraphics();  // Graphics context for drawing directly.
                                     // NOTE:  This is considered to be bad style!

        if ( evt.getButton() == MouseEvent.BUTTON3 ) {
                // User right-clicked at the point (x,y). Draw a blue oval centered 
                // at the point (x,y). (A black outline around the oval will make it 
                // more distinct when shapes overlap.)
            g.setColor(Color.BLUE);  // Blue interior.
            g.fillOval( x - 30, y - 15, 60, 30 );
            g.setColor(Color.BLACK); // Black outline.
            g.drawOval( x - 30, y - 15, 60, 30 );
            rightMouse = true;  // Draw with blue ovals in mouseDragged.
        }
        else {
                // User left-clicked (or middle-clicked) at (x,y). 
                // Draw a red rectangle centered at (x,y).
            g.setColor(Color.RED);   // Red interior.
            g.fillRect( x - 30, y - 15, 60, 30 );
            g.setColor(Color.BLACK); // Black outline.
            g.drawRect( x - 30, y - 15, 60, 30 );
            rightMouse = false;  // Draw with red rectangles in mouseDragged.
        }

        g.dispose();  // We are finished with the graphics context, so dispose of it.
        

    } // 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 draw a rect or oval at the
     *  current mouse position.
     */
    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.
        Graphics g = getGraphics();  // Graphics context for drawing directly.
                                     // NOTE:  This is considered to be bad style!
        if ( rightMouse ) {
                // The user is using the right mouse button; draw an oval.
            g.setColor(Color.BLUE);  // Blue interior.
            g.fillOval( x - 30, y - 15, 60, 30 );
            g.setColor(Color.BLACK); // Black outline.
            g.drawOval( x - 30, y - 15, 60, 30 );
        }
        else {
            g.setColor(Color.RED);   // Red interior.
            g.fillRect( x - 30, y - 15, 60, 30 );
            g.setColor(Color.BLACK); // Black outline.
            g.drawRect( x - 30, y - 15, 60, 30 );
        }
        g.dispose();  // We are finished with the graphics context, so dispose of it.
    } // 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 routine is required by the MouseMotionListener interface.

    public void mouseMoved(MouseEvent evt) { }

} // end class SimpleStamperWithDrag

[ Exercises | Chapter Index | Main Index ]