CPSC 225, Spring 2011, April 11
Tutorial on Mouse Tools

The purpose of this tutorial is to guide you through the creation and use of a set of mouse tools in a simple painting program. The goals are to learn: (1) how to implement dragging; (2) how to do so in an object-oriented way; and (3) how to let the user select tools using a group of JRadioButtonMenuItems. As part of this, the tutorial shows how to create and use a menu bar.

All of the code for this program will be in one big class, which is practical only for fairly small programs.

you will need the program MouseToolsFrame.java and the folder of icon images, icons, which can be found in the directory /classes/s11/cs225/tutorials.

The executable jar file, MouseToolsComplete.jar, in the same directory, contains a version of the program in which all the steps of this tutorial have been completed.

You should at some point read the file MouseToolsFrame.java, including the comments!


A Tools Menu

By a "tool" here, I mean an object that responds to mouse actions that the user makes on the display area of the program. A tool is really just a MouseListener that has been added to the display. The instance variable currentTool records the tool that is currently in use. A "Tools" menu will allow the user to select which tool to use.

The MouseToolsFrame program already has a menu bar. The method makeMenuBar creates the menu bar, the "File" menu, and the commands that are in the "File" menu. The commands are actually carried out by an object of type MenuHandler, which is a nested class defined later in the program.

To add a "Tools" menu to the program, you need to create the menu and its commands in the makeMenuBar method, and you need to add code for carrying out the commands to the MenuHandler class.

In makeMenuBar, create a "Tools" menu and add it to the menu bar, in exactly the same way that the "File" menu is created.

It's nice for the commands in the "Tools" menu to be radio buttons, since that gives a visual indication to the user of which tool is currently selected. Radio buttons in a menu are of type JRadioButtonMenuItem. To work properly as a group, the radio buttons must be in a ButtonGroup, which will make sure that only one button is selected at a time. Create the ButtonGroup by saying:

              ButtonGroup group = new ButtonGroup();

Now create a JRadioButtonMenuItem for each tool. You must add an ActionListener to the radio button, and you must add the radio button to the tools menu and to the button group. For example:

              JRadioButtonMenuItem curveCmd = new JRadioButtonMenuItem("Curve");
              curveCmd.addActionListener(menuHandler);
              toolsMenu.add(curveCmd);
              group.add(curveCmd);

You have to do this for each mouse tool that you implement. For the curve tool, since it is the default, the command should initially be selected:

              curveCmd.setSelected(true);

Now, down in the MenuHandler class, you have to say what to do when the user selects the "Curve" command from the menu. You should remove the current tool, whatever it is, from the display as a mouse listener, make the new tool object, and add that new tool as a mouse listener. A curve tool is defined as an object of type CurveTool.

              else if (cmd.equals("Curve")) {
                  display.removeMouseListener(currentTool);
                  currentTool = new CurveTool();
                  display.addMouseListener(currentTool);
              }

A Drag Tool

The icon images in this program are there only to give you something to drag. Note that the program uses a buffered image to hold the user's drawing, but the icons are not part of that image. They are drawn on top of the contents of the buffered image, and you can drag them around without disturbing the contents of the drawing. See the paintComponent method in the nested Display class to see how the icons are drawn.

To implement dragging, you will need a drag tool. Make a copy of the nested class CurveTool, and name the copy DragTool.

Add a "Drag Icon" command to the "Tools" menu. And add code to the MenuHandler class to respond to this command, by installing an objet of type DragTool as the current tool. The code for all this is very much the same as what was done for the Curve tool in the previous section.

Now, you have to make the drag tool drag icons, instead of drawing curves!

A drag operation in Java must be implemented in three different methods: mousePressed(), mouseDragged(), and mouseReleased(). The mouseDragged() method is called over and over as the user drags the mouse. Instance variables are used to remember information from one method call to the next. In particular, it is often necessary to remember the starting position of the mouse (where the mouse was first pressed) and the mouse position from the previous method call. In the program, these values are stored in startX, startY, prevX, and prvfY. Another instance variable, dragging, is used to remember whether or not a drag operation is actually in progress.

For the drag tool, not every mouse press starts a drag operation. To drag an icon, the user must click on that icon. The value of dragging should be set to true only if the user actually clicks an icon. Furthermore, during the drag, you have to remember which icon was clicked. The icon images and their positions are stored in two arrays, iconImages and iconPositions. In the DragTool class, as the user drags the mouse, you have to change one of the points in iconPositions. You need to remember which item in the array is changing, so you need another instance variable such as iconIndex to store the array index of the icon that is being dragged.

Here is a method that searches for the icon that contains a point (x,y). It returns -1 if no icon contains the point (x,y). Otherwise, it returns the array index for the icon that contains (x,y):

              private int clickedIcon( int x, int y ) {
                 for (int i = iconPositions.length -1; i >= 0; i--) {
                    int iconX = iconPositions[i].x;
                    int iconY = iconPositions[i].y;
                    if ( x > iconX && x < iconX+32 && y > iconY && y < iconY+32 )
                       return i;
                 }
                 return -1;
              }

Modify the mousePressed method in DragTool to set dragging to true only if one of the icons has been clicked. In that case, also record the array index for the icon that is being dragged.

Finally, modify the mouseDragged methodso that it moves the icon. The amount by which the icon must be moved is the amount by which the mouse has moved from its previous position. The is, you have to add

              int dx = evt.getX() - prevX;
              int dy = evt.getY() - prevY;

to the current position of the icon.

Don't forget to call display.repaint() so that the change will be shown on the screen. (To be more efficient, you could call repaintDisplayRect() twice, once for the rectangle that contains the icon before it is moved and once for the rectangle that will contain it after it is moved.)


A Filled Rectangle Tool

You should add a tool to the program that can be used for drawing filled rectangles. (If you like, you can then go on to add tools for other kinds of shapes: Straight lines, Filled Ovals, Outline Rectangles, and Outline Ovals. All of these work in a similar way to the Filled Rectangle tool. In fact, you could do them all with a single ShapeTool class, which uses an instance variable to say which type of shape is being drawn.)

To start, add a "Filled Rect" command to the "Tools" menu, and create a FilledRectTool class -- or a ShapeTool class if you want to be more general -- by making another copy of CurveTool.

For the curve tool, the curve is drawn to the off-screen image as the user drags the mouse. For a shape tool, that won't work. The rectangle changes shape as the user drags the mouse, and parts of the image can be covered up by the rectangle and then uncovered. You don't want to destroy a part the image if it is only temporarily covered by the rectangle.

Here is the solution: The rectangle is not drawn into the off-screen image until the user releases the mouse (in the mouseReleased method). While the mouse is being dragged, the rectangle must be drawn on top of the contents of the image, and that must be done by the paintComponent method of the Display class (just as is already done for the icon images).

Add a method void draw(Graphics g) to the tool class. It should draw a filled rectangle in the graphics context g with corners at (startX,startY) and (prevX,prevY). (You have to use the method g.filledRect(x,y,width,height) to draw the rectangle, but you only know two corners of the rectangle See the repaintDisplayRect method to see how to convert the corners of the rectangle into the data that you need for the fillRect method.)

The draw() method should be called in mouseReleased to draw the rectangle to the off-screen canvas. In mouseDragged, you only have to set the values of prevX and prevY and call display.repaint().

If the current tool is a FilledRectTool (or other shape tool), then the paintComponent method in the Display class has to call the tool's draw method to draw the shape on top of the canvas. This can be done by saying, for example,

              if ( currentTool instanceof FilledRectTool )
                  ((FilledRectTool)currentTool).draw(g);

(Note that it would make sense to change the names of prevX and prevY to currentX and currentY for this tool.)


A Use for mouseMoved!

The message label at the bottom of the window is there to give us an example of using the mouseMoved method. Just add the following mouse motion listener to the display, after it has been created in the MouseToolsFrame constructor:

     display.addMouseMotionListener( new MouseMotionListener() {
            public void mouseMoved(MouseEvent evt) {
                message.setText(String.format("(x,y) = (%d,%d)", evt.getX(), evt.getY()));
            }
            public void mouseDragged(MouseEvent evt) {
                message.setText(String.format("(x,y) = (%d,%d)", evt.getX(), evt.getY()));
            }
        });

As the user moves or drags the mouse, this puts the current location of the mouse in the message label.

As a more interesting example, when the drag tool is being used, you might change the cursor when the mouse is over one of the icons that can be dragged. The command

            display.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

will set the cursor for the display to look like a hand, and

            display.setCursor(Cursor.getDefaultCursor());
will set it to the default. You can add some code to the mouseMoved method in the above mouse motion listener to check whether the current tool is a DragTool and, if it is, to use a hand cursor when the mouse is over one of the icons.