CS 202 - Computer Science II - Spring 2008
Layouts and Event Handlers


Loyola College > Department of Computer Science > Dr. James Glenn > CS 202 > Examples and Lecture Notes > Layouts and Event Handlers

Incomplete code is available in a Java archive.


This applet requires Java 5 or later and so will not run in some browsers.

TrackDisplay.java

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

public class TrackDisplay extends JFrame
{
    // these components are referred to by name in the event handlers
    // and so need to be declared as fields

    private JTextField trackNameField;
    private JCheckBox shuffleBox;

    private TrackList tracks;

    public TrackDisplay()
    {
	super("MP3 Player");

	tracks = new TrackList("file:tracks.txt");

	Container cp = getContentPane();
	cp.setLayout(new BorderLayout());

	// create a text field to show the current track

	trackNameField = new JTextField();
	trackNameField.setEditable(false);
	trackNameField.setText(tracks.getCurrentTrackName());

	// make the panel that will go along the bottom edge (SOUTH) of
	// the window

	JPanel buttonPanel = new JPanel(new GridLayout(1, 3));

	// make the checkbox and buttons and register event handlers
	// for the events they generate

	shuffleBox = new JCheckBox("Shuffle");
	shuffleBox.addItemListener(new ShuffleListener());

	JButton prevButton = new JButton("Previous");
	prevButton.addActionListener(new PreviousListener());

	JButton nextButton = new JButton("Next");
	nextButton.addActionListener(new NextListener());

	// add those three components to the panel

	buttonPanel.add(shuffleBox);
	buttonPanel.add(prevButton);
	buttonPanel.add(nextButton);

	// add the text field and the panel with the other 3 components to
	// the window

	cp.add(trackNameField, BorderLayout.CENTER);
	cp.add(buttonPanel, BorderLayout.SOUTH);

	setSize(300, 75);
	setDefaultCloseOperation(EXIT_ON_CLOSE);
	setVisible(true);
    }

    // by declaring the following three event-handling classes as inner
    // classes, we automatically get access to the fields of the outer class
    // (and the methods too, although we do not use them here)

    /**
     * The event handler for the "Previous" button.
     */

    private class PreviousListener implements ActionListener
    {
	public void actionPerformed(ActionEvent e)
	{
	    tracks.goToPreviousTrack();
	    trackNameField.setText(tracks.getCurrentTrackName());
	}
    }

    /**
     * The event handler for the "Next" button.
     */

    private class NextListener implements ActionListener
    {
	public void actionPerformed(ActionEvent e)
	{
	    tracks.goToNextTrack();
	    trackNameField.setText(tracks.getCurrentTrackName());
	}
    }

    /**
     * The event handler for the "Shuffle" check box.
     */

    private class ShuffleListener implements ItemListener
    {
	public void itemStateChanged(ItemEvent e)
	{
	    tracks.setShuffle(shuffleBox.isSelected());
	    trackNameField.setText(tracks.getCurrentTrackName());
	}
    }

    public static void main(String[] args)
    {
	new TrackDisplay();
    }
}

TrackList.java

import java.util.*;
import java.io.*;

/**
 * A list of tracks that can be iterated through forwards or backwards.
 * For the purposes of iterating through the tracks, "next" either means
 * next in sequential order or, if shuffle mode has been selected,
 * another randomly selected track (take heed, Toyota!).  In shuffle mode,
 * "previous" means the previously played track, although this implemention
 * puts a cap on the number of previous tracks it remembers.
 *
 * @author Jim Glenn
 * @version 0.1 3/19/2008
 */

public class TrackList
{
    /**
     * The list of track names.
     */

    private List< String > tracks;

    /**
     * The current shuffle order, given as indices into <CODE>tracks</CODE>.
     */

    private List< Integer > shuffleOrder;

    /**
     * The index of the current track, as an index into <CODE>tracks</CODE>
     * or <CODE>shuffleOrder</CODE> depending on whether shuffle mode is
     * active or not.
     */

    private int currentTrack;

    /**
     * A flag set to true if shuffle mode is active.
     */

    private boolean shuffle;

    /**
     * Creates a list of tracks from the file with the given name.  The
     * file should have one track name per line; the tracks will be stored
     * in this list in the same order they were listed in the file.
     *
     * @param fname the name of a file containing track names
     */

    public TrackList(String fname)
    {
	try
	    {
		currentTrack = 0;
		shuffle = false;
		shuffleOrder = null;
		tracks = new LinkedList< String >();

		BufferedReader in = new BufferedReader(new FileReader(fname));
		String line;
		while ((line = in.readLine()) != null)
		    tracks.add(line);

		in.close();
	    }
	catch (IOException e)
	    {
		// Hey!  Someone put some error handling in here, please!
	    }
    }

    /**
     * Returns the index of the current track.
     *
     * @return the index of the current track
     */
   
    public int getCurrentTrackNumber()
    {
	if (shuffle)
	    return shuffleOrder.get(currentTrack);
	else
	    return currentTrack;
    }

    /**
     * Returns the name of the current track.
     *
     * @return the name of the current track
     */

    public String getCurrentTrackName()
    {
	return tracks.get(getCurrentTrackNumber());
    }

    /**
     * Turns shuffle mode on or off.  When shuffle mode is turned on after
     * being off, the current track is changed to a randomly selected one.
     *
     * @param newSetting the new shuffle setting; <CODE>true</CODE> for on
     */

    public void setShuffle(boolean newSetting)
    {
	if (shuffle != newSetting)
	    {
		shuffle = newSetting;

		if (shuffle)
		    {
			shuffleOrder = makeRandomTrackList();
			currentTrack = 0;
		    }
		else
		    {
			// change from index into shuffleOrder to index
			// into tracks

			currentTrack = shuffleOrder.get(currentTrack);
		    }
	    }
    }

    /**
     * Returns a randomly ordered list of track numbers.
     */

    private List< Integer > makeRandomTrackList()
    {
	// make list of track numbers (indices into tracks list),
	// initially 0, ..., length - 1

	List< Integer > order = new LinkedList< Integer >();

	for (int i = 0; i < tracks.size(); i++)
	    order.add(i);

	// randomize by swapping each position with a randomly chosen position

	for (int i = 0; i < tracks.size(); i++)
	    {
		int swapPos = (int)(Math.random() * tracks.size());
		int temp = order.get(i);
		order.set(i, order.get(swapPos));
		order.set(swapPos, temp);
	    }

	return order;
    }

    /**
     * Selects the next track in this list.  In shuffle mode this is
     * a randomly selected track.  With shuffle mode off, this is the
     * next track in the original list, wrapping around to the beginning.
     */

    public void goToNextTrack()
    {
	if (shuffle)
	    {
		currentTrack++;

		// check if we've run through the shuffled list...

		if (currentTrack >= shuffleOrder.size())
		    {
			// ...and make a new list if we have

			List< Integer > newOrder = makeRandomTrackList();

			// Prevent the last track from being played twice
			// in a row by removing it from the new list
			// (note that the last tracks.size() entries on
			// shuffleOrder are still all the valid indices).
			// Note that the next-to-last track may endf up being
			// at the front of newOrder, so we might play it
			// twice with only one track between.  To avoid
			// such problems we could extend this to remove
			// the last 10% of indices from the new list.
			// (To anyone seeking a patent on "A Method for
			// Randomizing Track Lists that Avoids Duplications"
			// or some such nonsense: this entire piece of
			// code took me 30 minutes to write, so any such
			// invention clearly fails the non-obviousness test.)

			newOrder.remove(new Integer(shuffleOrder.get(shuffleOrder.size() - 1)));
			shuffleOrder.addAll(newOrder);
			
			// forget tracks in old shuffles (to save space)

			if (shuffleOrder.size() > 2 * tracks.size())
			    {
				for (int i = 0; i < tracks.size() - 1; i++)
				    shuffleOrder.remove(0);
				currentTrack -= (tracks.size() - 1);
			    }
		    }
	    }
	else
	    {
		currentTrack = (currentTrack + 1) % tracks.size();
	    }
    }

    /**
     * Selects the previous track in this list.  In shuffle mode this
     * is the previously played track, up to the limit of saved
     * history.  If the limit has been reached, this method does
     * nothing.  With shuffle mode off, the new track is the previous
     * one in the original list, wrapping around to the end.
     */

    public void goToPreviousTrack()
    {
	if (shuffle)
	    {
		currentTrack = Math.max(0, currentTrack - 1);
	    }
	else
	    {
		currentTrack = (currentTrack + tracks.size() - 1) % tracks.size();
	    }
    }

    public String toString()
    {
	StringBuffer result = new StringBuffer();

	if (shuffle)
	    {
		for (int i = 0; i < shuffleOrder.size(); i++)
		    {
			if (currentTrack == i)
			    result.append(">" + tracks.get(shuffleOrder.get(i)) + "<\n");
			else
			    result.append(" " + tracks.get(shuffleOrder.get(i)) + " \n");
		    }
	    }
	else
	    {
		for (int i = 0; i < tracks.size(); i++)
		    {
			if (currentTrack == i)
			    result.append(">" + tracks.get(i) + "<\n");
			else
			    result.append(" " + tracks.get(i) + " \n");
		    }
	    }

	return result.toString();
    }
}

tracks.txt

Muse - Exopolitics
Public Enemy - They Call Me Flavor
Oak Ridge Boys - Elvira
Celine Dion - My Heart Will Go On
Radiohead - 4 Minute Warning
Cam'ron - Killa Cam
Usher - Caught Up
Interpol - Rest My Chemistry
Gwen Stefani - Early Winter
The Beatles - I Am the Walrus
This code can also be downloaded from the files
TrackDisplay.java, TrackList.java, and tracks.txt.