Solution for
Programming Exercise 6.4


THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

Exercise 6.4: In Exercise 3.5, you drew a checkerboard. For this exercise, write a checkerboard applet where the user can select a square by clicking on it. Hilite the selected square by drawing a colored border around it. When the applet is first created, no square is selected. When the user clicks on a square that is not currently selected, it becomes selected. If the user clicks the square that is selected, it becomes unselected. Assume that the size of the applet is 160 by 160 pixels, so that each square on the checkerboard is 20 by 20 pixels. Here is a working version of the applet:


Discussion

The solution to Exercise 3.5 already contains the code for drawing the checkerboard. I created my solution to this exercise as a modification of that program.

To keep track of which square is selected, if any, I added instance variables selectedRow and selectedCol. When no square is selected, selectedRow is -1 (and I don't care what selectedCol is). When a square is selected, selectedRow is the number of the row that contains that square and selectedCol is the number of the column that contains the selected square. Remember that rows and columns are numbered from 0 to 7. This makes some of the calculations easier than numbering them from 1 to 8.

The paint() method has to hilite the selected square. I do this by drawing a cyan border around the inside of the selected square. This is the new code that is added to the paint() method:

          if (selectedRow <= 0) {
                // Since there is a selected square, draw a cyan
                // border around it.
             g.setColor(Color.cyan);
             y = selectedRow * 20;
             x = selectedCol * 20;
             g.drawRect(x, y, 19, 19);
             g.drawRect(x+1, y+1, 17, 17);
          }

Since the squares are 20 pixels on each side, you might wonder why the first drawRect() command specifies a width and height of 19 instead of 20. In the fillRect() method that is used earlier in the paint() method to fill in the square, a width and height of 20 is used. Remember that the drawRect() method actually draws a rectangle whose width and height are one more than the values specified in the parameters. (Remember the bit about the pen that hangs one pixel outside the rectangle?)

To respond to user mouse clicks, the applet must implement the MouseListner interface. The init() method of the applet calls addMouseListener(this) to register the applet to listen for mouse events on itself. Of the five methods specified in the MouseListener interface, only mousePressed has a non-empty definition. This method must figure out which square the user clicked and adjust the values of the instance variables selectedRow and selectedCol accordingly.

Let's say that the user clicked at the point (x,y). The problem is to determine which square on the checkerboard contains that point. The column number of the square is obtained by dividing the x coordinate by the width of the squares. Since the squares are 20 pixels wide, the row number of the clicked square is x/20. For values of x between 0 and 19, this gives a column number of 0, which is correct. For the next 20 pixels, from 20 to 39, x/20 is 1, which is the correct column number. For the next strip of pixels, from 40 to 59, the answer is 2. And so on. Similarly, y/20 gives the row number of the square where the user clicked.

Once we know the row and column where the user clicked, we can compare them to selectedRow and selectedCol. If the values are the same, then the user clicked in a square that was already selected. We want to remove the hiliting. That can be done by setting selectedRow = -1, the value that indicates that no square is selected. Otherwise, the values of selectedRow and selectedCol are set to the row and column that the user clicked.

All this explains the reasoning behind the mousePressed() routine, which you can see below.


The Solution


    /*  
        This applet draws a red-and-black checkerboard.
        It is assumed that the size of the applet is 160
        by 160 pixels.  When the user clicks a square, that
        square is selected, unless it is already selected.
        When the user clicks the selected square, it is
        unselected.  If there is a selected square, it is
        hilited with a cyan border.
    */
       
    
    import java.awt.*;
    import java.awt.event.*;
    import java.applet.*;
    
    public class ClickableCheckerboard extends Applet 
                                           implements MouseListener{
                                           
       
       int selectedRow; // Row and column of selected square.  If no
       int selectedCol; //      square is selected, selectedRow is -1.
       
       
       public void init() {
             // Initialize the applet.  Set selectedRow to -1 to indicate
             // that no square is selected.  And listen for mouse events.
          selectedRow = -1;     
          addMouseListener(this);
       }
       
       
       public void update(Graphics g) {
             // Since the paint method paints the whole applet, there is
             // no need to fill it with the background color first.  To 
             // avoid flickering, redefine update so it just calls paint.
          paint(g);
       }
     
    
       public void paint(Graphics g) {
             // Draw the checkerboard and hilite the selected square, if any.
           
          int row;   // Row number, from 0 to 7
          int col;   // Column number, from 0 to 7
          int x,y;   // Top-left corner of square
        
          for ( row = 0;  row < 8;  row++ ) {
           
             for ( col = 0;  col < 8;  col++) {
                 x = col * 20;
                 y = row * 20;
                 if ( (row % 2) == (col % 2) )
                    g.setColor(Color.red);
                 else
                    g.setColor(Color.black);
                 g.fillRect(x, y, 20, 20);
             } 
           
          } // end for row
          
          if (selectedRow <= 0) {
                // Since there is a selected square, draw a cyan
                // border around it.
             g.setColor(Color.cyan);
             y = selectedRow * 20;
             x = selectedCol * 20;
             g.drawRect(x, y, 19, 19);
             g.drawRect(x+1, y+1, 17, 17);
          }
        
       }  // end paint()
       
       
       public void mousePressed(MouseEvent evt) {
              // When the user clicks on the applet, figure out which
              // row and column the click was in and change the
              // selected square accordingly.
              
          int col = evt.getX() / 20;   // Column where user clicked.
          int row = evt.getY() / 20;   // Row where user clicked.
          
          if (selectedRow == row && selectedCol == col) {
               // User clicked on the currently selected square.
               // Turn off the selection by setting selectedRow to -1.
             selectedRow = -1;
          }
          else {
                // Change the selection to the square the user clicked on.
             selectedRow = row;
             selectedCol = col;
          }
          repaint();
    
       }  // end mouseDown()
    
    
       public void mouseReleased(MouseEvent evt) { }
       public void mouseClicked(MouseEvent evt) { }
       public void mouseEntered(MouseEvent evt) { }
       public void mouseExited(MouseEvent evt) { }
    
    
    }   // end class


[ Exercises | Chapter Index | Main Index ]