Course Links

Exercises

Resources

External

Overview

Up to this point, the GUI programs we have developed have been rather simple in at least one sense: each control or potential user action typically only had one effect within the program. Today we will look at a GUI environment that is modal: the GUI may be in one of multiple possible modes, and the effect of an action (such as clicking the mouse) may have completely different effects depending on which mode is currently active. The application will consist of a canvas containing a set of points, and the two modes will be for Adding or Moving Points and Deleting Points. (Note that although adding and moving points are two different types of actions, they can share a mode because the former is activated by clicking the mouse, and the latter by dragging it.) Our application will allow the user to switch between modes using buttons on the side of the GUI, and the current mode will be indicated to the user via a text label.

Some human interface designers don't like modality, and prefer that all interfaces be modeless -- all actions are available at all times. The more actions that are available, the harder it is to keep each action distinct without using modes.

Class PointCanvas

The class PointCanvas will be a subclass of JComponent responsible for maintaining a list of points and displaying them as small circles. Most of the structure of this class is given to you below; you should fill in the paintComponent() implementation.

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

/**
 *  Implements a graphical canvas that displays a list of points.
 *
 *  @author  Nicholas R. Howe
 *  @version CSC 112, 18 April 2006
 */
class PointCanvas extends JComponent {
    /** The points */
    LinkedList<Point> points;

    /** Constructor */
    public PointCanvas() {
        points = new LinkedList<Point>();
        setMinimumSize(new Dimension(500,300));
        setPreferredSize(new Dimension(500,300));
    }

    /**
     *  Paints a red circle ten pixels in diameter at each point.
     *
     *  @param g The graphics object to draw with
     */
    public void paintComponent(Graphics g) {
        // FILL IN
    }
}

Note that for purposes of this lab, the class fields have been declared with package access (i.e., there are no public, private, or protected designations.) This means that your other classes may refer directly to the fields. Although this isn't the best design for most programs, we will use it in this lab so as to devote more time to other aspects of the program.

Class PointGUI

The class PointGUI and its nested helper classes (event listeners, etc.) will set up and run the GUI that allows the user to add, delete, and move points. The shell of this class appears below, but before presenting it, some details will help explain what is going on.

The state of the GUI has several parts. First of all, it must record the current mode. We could use an integer, or a string, or some other ad hoc code for this purpose, but the most appropriate way to do it is by defining a nested enum class with exactly two values. The class is called InputMode below, and its two values are ADD_POINTS and RMV_POINTS. Second, the GUI must have some way of knowing what point is being moved in drag operations. (This is called pointUnderMouse below, and must be set when a mouse press is detected, and unset when a release is detected. If a drag occurs while pointUnderMouse is set, and the mode is ADD_POINTS, then the point's location must be altered and the canvas redrawn.) Finally, the GUI must be able to access and change properties of the canvas and the label text from the event handlers, so these are also added as class fields.

The shell below handles all the code for setting up the GUI component layout and adding event handlers. However, the implementation of those event handlers has been left up to you. Before you begin, think about how each potential mouse action -- press, release, click, drag -- should affect the GUI state in each possible mode. Then you can write the handlers to match the requirements you have identified. In most cases, a particular event will only be significant for one mode or the other. But the click event must perform different things depending on the current mode. This is a perfect place to use a switch statement.

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

/**
 *  Implements a GUI for inputting points.
 *
 *  @author  Nicholas R. Howe
 *  @version CSC 112, 18 April 2006
 */
public class PointGUI {
    /** The graph to be displayed */
    private PointCanvas canvas;

    /** Label for the input mode instructions */
    private JLabel instr;

    /** The input mode */
    InputMode mode = InputMode.ADD_POINTS;

    /** Remembers point where last mousedown event occurred */
    Point pointUnderMouse;

    /**
     *  Schedules a job for the event-dispatching thread
     *  creating and showing this application's GUI.
     */
    public static void main(String[] args) {
        final PointGUI GUI = new PointGUI();
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    GUI.createAndShowGUI();
                }
            });
    }

    /** Sets up the GUI window */
    public void createAndShowGUI() {
        // Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);

        // Create and set up the window.
        JFrame frame = new JFrame("Graph GUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Add components
        createComponents(frame);

        // Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    /** Puts content in the GUI window */
    public void createComponents(JFrame frame) {
        // graph display
        Container pane = frame.getContentPane();
        pane.setLayout(new FlowLayout());
        JPanel panel1 = new JPanel();
        panel1.setLayout(new BorderLayout());
        canvas = new PointCanvas();
        PointMouseListener pml = new PointMouseListener();
        canvas.addMouseListener(pml);
        canvas.addMouseMotionListener(pml);
        panel1.add(canvas);
        instr = new JLabel("Click to add new points; drag to move.");
        panel1.add(instr,BorderLayout.NORTH);
        pane.add(panel1);

        // controls
        JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayout(2,1));
        JButton addPointButton = new JButton("Add/Move Points");
        panel2.add(addPointButton);
        addPointButton.addActionListener(new AddPointListener());
        JButton rmvPointButton = new JButton("Remove Points");
        panel2.add(rmvPointButton);
        rmvPointButton.addActionListener(new RmvPointListener());
        pane.add(panel2);
    }

    /** 
     * Returns a point found within the drawing radius of the given location, 
     * or null if none
     *
     *  @param x  the x coordinate of the location
     *  @param y  the y coordinate of the location
     *  @return  a point from the canvas if there is one covering this location, 
     *  or a null reference if not
     */
    public Point findNearbyPoint(int x, int y) {
        // FILL IN:
		// Loop over all points in the canvas and see if any of them
		// overlap with the location specified.  You may wish to use
		// the .distance() method of class Point.
    }

    /** Constants for recording the input mode */
    enum InputMode {
        ADD_POINTS, RMV_POINTS
    }

    /** Listener for AddPoint button */
    private class AddPointListener implements ActionListener {
        /** Event handler for AddPoint button */
        public void actionPerformed(ActionEvent e) {
            mode = InputMode.ADD_POINTS;
            instr.setText("Click to add new points or change their location.");
        }
    }

    /** Listener for RmvPoint button */
    private class RmvPointListener implements ActionListener {
        /** Event handler for RmvPoint button */
        public void actionPerformed(ActionEvent e) {
		    // FILL IN:
			// Model on the AddPointListener above.  Should change both mode and label text.
        }
    }

    /** Mouse listener for PointCanvas element */
    private class PointMouseListener extends MouseAdapter
        implements MouseMotionListener {

        /** Responds to click event depending on mode */
        public void mouseClicked(MouseEvent e) {
            switch (mode) {
            case ADD_POINTS:
				// FILL IN
				// If the click is not on top of an existing point, create a new one and add it to the canvas.
				// Otherwise, emit a beep, as shown below:
				Toolkit.getDefaultToolkit().beep();
                break;
            case RMV_POINTS:
				// FILL IN
				// If the click is on top of an existing point, remove it from the canvas's list of points.
				// Otherwise, emit a beep.
                break;
            }
            canvas.repaint();
        }

        /** Records point under mousedown event in anticipation of possible drag */
        public void mousePressed(MouseEvent e) {
            // FILL IN:  Record point under mouse, if any
        }

        /** Responds to mouseup event */
        public void mouseReleased(MouseEvent e) {
            // FILL IN:  Clear record of point under mouse, if any
        }

        /** Responds to mouse drag event */
        public void mouseDragged(MouseEvent e) {
			// FILL IN:
			// If mode allows point motion, and there is a point under the mouse, 
			// then change its coordinates to the current mouse coordinates & update display
        }

		// Empty but necessary to comply with MouseMotionListener interface.
        public void mouseMoved(MouseEvent e) {}
    }
}

Going Further

The principles developed here can be applied to many sorts of application interfaces, and extended to apply to more complicated user interactions. Additional mode buttons can be added to allow for additional functionality. By supplementing the GUI state with additional fields as necessary, and using switches on the current mode where required in the event handlers, the program can be extended while avoiding or minimizing potential confusion: each mode can be developed and operate relatively free of interference from the others.

A note of caution is appropriate in conclusion: although a modal GUI can be relatively simple to design, it is not always the easiest choice for users, who can easily become confused about which mode is active. For this reason, it is important to include visual feedback and other sorts of cues that will help the user remain oriented. Some designers eschew modes altogether; the original Mac interface was designed to be entirely modeless, in contrast to heavy use of modes in PC software at the time.

To Submit