Technical: Java
Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

Building the Slide Show



File: SlideShow.java


Contents

Overview
1) Declaring and Defining Constants

2) Declaring the Slide Show Data Members

3) Declaring our Application Menus

4) Creating the main entry point for the application

5) Initializing the SlideShow’s State

6) Setting up and Initializing our Application Controls

7) Initializing the Application Menus

8) Registering our Application Event Listeners

9) Implementing the Application Thread Model

10) Implementing togglePlaying( )

11) Implementing the oneStep( ) Method

12) Implementing the toggleFullScreen( ) method

13) Implementing toggleControls( )

14) Implementing doOnQuit( )

15) Implementing doAbout( )

16) Implementing the paint( ) method

17) Implementing setVisible( )

18) Registering Special MRJ Handlers

19) Creating a Inner Class to Handle Action Events

20) Responding to Action Events
Summary
Overview

slideshowThe slideshow is the main class of our application. Not only does it provide the main entry point to our application, it ties together all of the classes that we previously defined. It allows the user to select a number of image files which it will display sequentially when the user clicks on the play button. It creates a number of menu items that allows the user to specify options such as full screen and hide the control strip. It also provides facilities for advancing and going backwards through the image list.

This class demonstrates a number of interesting concepts such as how to handle menus, manipulate windows, and provide advanced Macintosh functionality via the MRJUtilites classes. With these classes, we can respond to core AppleEvents and handle files in a more mac-like manner.

Steps to Follow

Back to top

Step 1 - Declaring and Defining Constants

Before we start declaring our class, we must first import all of the packages that we will be using. The second set of imports are of interest. The com.apple.mrj packages are special MRJToolkit packages that give your application behaviors specific to the Macintosh. We will discuss these in more detail in Step 18 - Registering Special MRJ Handlers.

Our SlideShow class derives from java.awt.Frame. A frame is a top-level window (it is in fact derived from the window class) that has a border and title bar. We use a frame instead of a borderless window because we want to have a title bar for our window.

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.Vector;

import com.apple.mrj.MRJApplicationUtils;
import com.apple.mrj.MRJOpenDocumentHandler;
import com.apple.mrj.MRJQuitHandler;
import com.apple.mrj.MRJAboutHandler;

public class SlideShow extends Frame
{
    //Declare and define constants
    //Insert "SlideShow Constants"

Locate the SlideShow Constants clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.Vector;

import com.apple.mrj.MRJApplicationUtils;
import com.apple.mrj.MRJOpenDocumentHandler;
import com.apple.mrj.MRJQuitHandler;
import com.apple.mrj.MRJAboutHandler;

public class SlideShow extends Frame
{
    //Declare and define constants
    //Insert "SlideShow Constants"
    protected static final int SLEEP_DELAY = 1000;
    protected static final int WIDTH = 430;
    protected static final int HEIGHT = 270;

We declare three constants for our class. The first, SLEEP_DELAY, is the number of milliseconds to pause between slides before going to the next. The number 1000 corresponds to 1 second. The WIDTH constant is the default width of the slide viewing area while HEIGHT is the height of the area.

Now we will declare the class data members.

Back to top

Step 2 - Declaring the Slide Show Data Members

We will now declare a number of data members to store internal state such as whether or not we are running in full screen, mode, and for our list of images we are displaying.

    protected static final int WIDTH = 430;
    protected static final int HEIGHT = 270;
    //Declare data members
    //Insert "SlideShow data members"

Locate the SlideShow data members clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    protected static final int WIDTH = 430;
    protected static final int HEIGHT = 270;

    //Declare data members
    //Insert "SlideShow data members"
    protected Vector files;
    protected Image currentImage;
    protected int currentIndex;
    protected Boolean isFullScreen;
    protected Boolean isPlaying;
    protected PlayRunnable playRunnable;
    protected Thread thread;
    protected FileDialog openFileDialog1;
    protected Controller controls;
    protected AboutDialog aboutDialog1;

We declare a number of data members that we will use to store internal data. All are declared protected so that derived classes have access to them, but clients may only access the data through pre-defined data accessor routines. This is good coding practice and prevents users from causing problems by directly accessing this data. Since there are a lot of variables here, we will examine them one at a time.

protected Vector files;

First, we declare a vector to store our image files that will be successively displayed as the slide show. This vector is similar to the vector of images used by the button, but instead of button images, this vector holds the set of slides to be displayed.

protected Image currentImage;
protected int
currentIndex;

Next, we declare an image variable that stores the image currently being displayed, and an integer that stores the index in the vector of the image being displayed.

protected Boolean isFullScreen;
protected Boolean
isPlaying;

We declare two Booleans to keep track of whether we are currently in full-screen mode, and one to keep track of whether the slide show is playing. In full-screen mode, we black out the desktop, and center the images in that area. If the image is larger than the screen, it is scaled to fit.

protected PlayRunnable playRunnable;

This is our Runnable object which contains the body of our thread. We use this to automatically progress through the slides when the user clicks the play button, or chooses the play menu item.

protected Thread thread;

This is the thread we use to execute our PlayRunnable from.

protected FileDialog openFileDialog1;



We declare a file dialog for creation and storage of our file management object. Our FileDialog is used to facilitate the selection of images to be part of the slide show using the standard file mechanism. If you are using MacOS 8.5 or later, this dialog will use Navigation services.

protected Controller controls;

Our controller variable is used to store an instance of our Controller class which contains the buttons and user interface for our slide show. The controller sends action events to our slide show which we will respond to based on the message.

protected AboutDialog aboutDialog1;

Lastly, we declare an instance of our AboutDialog class from which we will instantiate our about box if the user chooses About SlideShow from the Apple Menu.

Now that we have our data members, it is time to declare our menu items.

Back to top

Step 3 - Declaring our application Menus

The application defines a series of menus and menu items that may be used to add images to the slide show and configure the viewer.

    protected Controller controls;
    protected AboutDialog aboutDialog1;
    //DECLARE_MENUS
    //Declare Menus, Menu Items and the Menu Bar
    //Insert "SlideShow declare menus"

Locate the SlideShow declare menus clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    protected Controller controls;
    protected AboutDialog aboutDialog1;

    //DECLARE_MENUS
    //Declare Menus, Menu Items and the Menu Bar
    //Insert "SlideShow declare menus"
    protected MenuBar menuBar1;
    protected Menu fileMenu;
    protected MenuItem openMenuItem;
    protected MenuItem quitMenuItem;
    protected Menu slideShowMenu;
    protected MenuItem playMenuItem;
    protected MenuItem backMenuItem;
    protected MenuItem forwardMenuItem;
    protected Menu optionsMenu;
    protected MenuItem controlsMenuItem;
    protected MenuItem fullScreenMenuItem;

We are declaring our menus and menu items that will be used in our application. The first object is our main menu bar object which will hold all of our menus.

filemenu

The next three items are the File menu, the Open item of the File menu, and the Quit item of the File menu. This menu will look like the image above. When we build the menu, we will be placing a separator between the Open and Quit items. This will be covered in Step 7 - Initializing the Application Menus.

slideshowmenu

Next, we declare the Slideshow menu and its three menu items: Toggle Play, Back, and Forward. This menu is pictured above. We will be adding the Command Key equivalents to the menu items in Step 7 - Initializing the Application Menus.

optionsmenu

Lastly, we declare three variables for the final menu, the Options menu and its two menu items, Toggle Controls, and Toggle Fullscreen.

Now that we have all of our class data members declared, it is time to work on our main( ) routine, the main entry point for our application.

Back to top

Step 4 - Creating the main entry point for the application

The main entry point of an application is the initial execution point. It is the routine that gets called first when the application is run. In our main( ) routine, we will create our slideshow class, make our frame visible, and register some special MRJ-specific handlers that will provide a more Mac-like experience for the user.

/**
 * The entry point to our application
 */
 static public void main(String args[])
 {
     //Instantiate our SlideShow, make it visible, and register 
//our MRJ handlers. //Insert "SlideShow main"

Locate the SlideShow main clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

/**
 * The entry point to our application
 */
 static public void main(String args[])
 {
     //Instantiate our SlideShow, make it visible, and register 
//our MRJ handlers. //Insert "SlideShow main"
     SlideShow slideShow = new SlideShow( );
     slideShow.setVisible(true);
     slideShow.registerMRJHandlers( );
 }

The first step in our main routine is to create an instance of our SlideShow object. Once this object is created in our constructor, we call setVisible( ) with a Boolean parameter to make the frame visible. Finally we call our registerMRJHandlers( ) method. This method installs a handler for handling AppleEvents such as a QuitApplication AppleEvent or an OpenDocument AppleEvent. We will talk about this routine in more detail in Step 18 - Registering Special MRJ Handlers .

Next we will implement the SlideShow constructor.

Back to top

Step 5 - Initializing the SlideShow’s State

The constructor of the SlideShow has several important functions such as creating the application menus, initializing the state, initializing controls, and registering listeners. The first step we will perform is initializing the application’s state.

public SlideShow( )
{	
    //INIT_STATE
    //Initialize state information
    //Insert "SlideShow init state"

Locate the SlideShow init state clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

public SlideShow( )
{     
    //INIT_STATE
    //Initialize state information
    //Insert "SlideShow init state"
    isFullScreen = false;
    isPlaying = false;
    files = new Vector( );
    currentImage = null;
    currentIndex = -1;

We initialize several of the data members to give them initial values. When we first start up, we don’t want to be full screen, so we set isFullScreen to false, and we don’t want to be playing initially, so we set isPlaying to false as well.

We create a new vector object and store it in our files variable. This vector will keep track of the images in our slideshow. We use a vector object for this storage because that allows us to support an arbitrary number of images since the data structure is dynamically resizable.

Since we don’t have any initial images, we set the currentImage variable to null and the currentIndex variable to -1. We choose -1 because it is not a valid vector index and also because it is a recognizable value. Any attempt to use it would throw an exception that we would easily be able to track down to this initialization. If we would have used a 0 or 1, this would be harder to debug since this could be a valid vector index.

Next we will set up and initialize our controls.

Back to top

Step 6 - Setting up and Initializing our Application Controls

Our application contains a number of controls and objects that need to be initialized in our constructor.

    isFullScreen = false;
    isPlaying = false;
    files = new Vector( );
    currentImage = null;
    currentIndex = -1;
    //INIT_CONTROLS
    //Setup and configure our SlideShow
    //Insert "SlideShow init controls"

Locate the SlideShow init controls clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    isFullScreen = false;
    isPlaying = false;
    files = new Vector( );
    currentImage = null;
    currentIndex = -1;

    //INIT_CONTROLS
    //Setup and configure our SlideShow
    //Insert "SlideShow init controls"
    setLayout(null);
    setVisible(false);
    setSize(WIDTH, HEIGHT);
    Dimension screenSize = 
       Toolkit.getDefaultToolkit( ).getScreenSize( );
    setLocation((screenSize.width - WIDTH) / 2, 
       (screenSize.height - HEIGHT) / 2);
    setBackground(Color.black);
    openFileDialog1 = new FileDialog(this);
    openFileDialog1.setMode(FileDialog.LOAD);
    openFileDialog1.setTitle("Open");
    openFileDialog1.setFilenameFilter(new ImageNameFilter( ));
    setTitle("SlideShow");
    controls = new Controller(this);

Let’s look at these initialization statements line by line in order to better understand what they do.

setLayout(null);

We do not want to use a layout manager for our SlideShow window because we will be drawing the contents of the window ourselves. Thus, we set the layout manager to null in order to specify no layout.

setVisible(false);

We hide the window during its construction by calling setVisible( ) with false as the parameter. This is good practice, as we don’t want the user to see the window pieces being constructed.

setSize(WIDTH, HEIGHT);

We set the initial size of our window be 430 pixels wide and 270 pixels tall. The WIDTH and HEIGHT values are the constants we defined previously.

Dimension screenSize = Toolkit.getDefaultToolkit( ).getScreenSize( );

We need to get the width and height of the screen that we are displaying on so that we can center our window, or resize the window to the size of the screen if we are in full screen mode. To get the screen size, we use a method in the java.awt.Toolkit class. The toolkit is a mechanism that gives us access to the underlying java peer classes, or native classes on which the awt is built. For example, in the case of java.awt.Window, there is a peer class that uses the native system call on the platform to create that window. On a Macintosh, the peer class for window uses the Macintosh Toolbox and Window Manager to create the window that corresponds to the Java object. On Windows, the peer class uses the MFC and the Windows API to create a native window.

By retrieving the default toolkit, we have access to platform specific information such as the call to getScreenSize( ) which uses a native call to retrieve the dimension of our screen.

setLocation((screenSize.width - WIDTH) / 2, (screenSize.height - HEIGHT) / 2);

We use some basic math to center the window on the screen. The setLocation( ) call, positions the top left-hand corner of the window based on the parameters which are offsets from the top and left corner of the screen.

setBackground(Color.black);

We want the window to have a black fill color, so we set the background to black.

openFileDialog1 = new FileDialog(this); openFileDialog1.setMode(FileDialog.LOAD); openFileDialog1.setTitle("Open"); openFileDialog1.setFilenameFilter(new ImageNameFilter( ));

Our application uses a file dialog to allow the user to specify the images to be used as part of the slide show. We create a new FileDialog object and assign it to our variable. A file dialog can be used to either open files, or save files. The function of the dialog is specified by setting the mode with the setMode() call. We want to use our dialog to open files, so we call setMode( ) with FileDialog.LOAD as the parameter. Next, we set the title of the dialog to “Open”, and specify a file filter to be used by the dialog. Our file filter will look at the names of the files and only display those names which end in a ".gif" or ".jpg" extension. We will look at this filter in detail when we examine the ImageNameFilter file.

setTitle("SlideShow");

We set the title of the window to “SlideShow” for lack of a better name.

controls = new Controller(this);

Lastly, we create our compound controller object that contains all of the buttons and controls for controlling our application. We pass ourselves as the parent frame parameter to the constructor of the Controller.

Next, we will initialize our application menus.

Back to top

Step 7 - Initializing the Application Menus

We are now ready to create our application menu bar and associated menus and menu items.

    openFileDialog1.setTitle("Open");
    openFileDialog1.setFilenameFilter(new ImageNameFilter( ));
    setTitle("SlideShow");
    controls = new Controller(this);
    //INIT_MENUS
    //Create, configure, and setup the menubar, 
    //menus, and menu items.
    //Insert "SlideShow init menus"

Locate the SlideShow init menus clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    openFileDialog1.setTitle("Open");
    openFileDialog1.setFilenameFilter(new ImageNameFilter( ));
    setTitle("SlideShow");
    controls = new Controller(this);

    //INIT_MENUS
    //Create, configure, and setup the menubar, 
    //menus, and menu items.
    //Insert "SlideShow init menus"
    menuBar1 = new MenuBar( );
		
    //File menu
    fileMenu = new Menu("File");
    openMenuItem = new MenuItem("Open...");
    openMenuItem.setShortcut(new MenuShortcut
                            (KeyEvent.VK_O,false));
    fileMenu.add(openMenuItem);
    fileMenu.addSeparator( );
    quitMenuItem = new MenuItem("Quit");
    quitMenuItem.setShortcut(new MenuShortcut
                            (KeyEvent.VK_Q,false));
    fileMenu.add(quitMenuItem);
    menuBar1.add(fileMenu);
		
    //SlideShow menu
    slideShowMenu = new Menu("SlideShow");
    playMenuItem = new MenuItem("Toggle Play");
    playMenuItem.setShortcut(new MenuShortcut
                            (KeyEvent.VK_P,false));
    slideShowMenu.add(playMenuItem);
    backMenuItem = new MenuItem("Back");
    backMenuItem.setShortcut(new MenuShortcut
                            (KeyEvent.VK_OPEN_BRACKET,false));
    slideShowMenu.add(backMenuItem);
    forwardMenuItem = new MenuItem("Forward");
    forwardMenuItem.setShortcut(new MenuShortcut
                            (KeyEvent.VK_CLOSE_BRACKET,false));
    slideShowMenu.add(forwardMenuItem);
    menuBar1.add(slideShowMenu);
		
    //Options menu
    optionsMenu = new Menu("Options");
    controlsMenuItem = new MenuItem("Toggle Controls");
    optionsMenu.add(controlsMenuItem);
    fullScreenMenuItem = new MenuItem("Toggle Full Screen");
    optionsMenu.add(fullScreenMenuItem);
    menuBar1.add(optionsMenu);
    setMenuBar(menuBar1);

Since this is a lot of code, we will look at it in blocks. First, we will set up the main menu bar and File menu.

menuBar1 = new MenuBar( );

We create a main menu bar object and store it in our data member.

filemenu

fileMenu = new Menu("File");

We create each of our menus by creating a new java.awt.Menu object with the string of the menu name. In this case, we create the File menu by using the string “File”, and assigning the result to our local data member.


openMenuItem = new MenuItem("Open...");

Menu items are created similarly. For every item we want to create, we create a new instance of java.awt.MenuItem with the string for the item as we want it displayed in the menu. Here we use the string “Open…”.

openMenuItem.setShortcut(new MenuShortcut (KeyEvent.VK_O,false));

To make our application more user friendly, we assign menu shortcuts to our menu items. These shortcuts translate to Command keys on the Macintosh, and accelerator keys on Windows platforms. To make a menu shortcut, we call setShortcut( ) from the specific menu item, and pass a java.awt.MenuShortcut object which we are creating in place. The menu shortcut constructor takes a virtual key code that specifies the key for the shortcut and a Boolean which specifies whether the Shift key needs to be pressed or not. In our case, we use the Macintosh standard Command-O for our Open menu item, and we pass false to specify that the user does not need to hold down Shift-Command-O to perform the shortcut.

fileMenu.add(openMenuItem);
fileMenu.addSeparator( );

We add our newly created open menu item to our File menu and add a separator. This is a horizontal line that will appear between our open item and the Quit item.

quitMenuItem = new MenuItem("Quit");
quitMenuItem.setShortcut(new MenuShortcut (KeyEvent.VK_Q,false)); fileMenu.add(quitMenuItem);
menuBar1.add(fileMenu);

slideshowmenuNow that we have walked through the Open menu item, it is a lot easier to understand the code for the Quit menu item. We create a new menu item and assign it to our quitMenuItem data member. We add a shortcut (Command-Q) for the item, and add the item to the File menu. Lastly, we add the completed File menu to our main menu bar. Menu items are added from the top to the bottom to menus, and menus are added from left to right in the menu bar.


The remaining two items are very straightforward.

slideShowMenu = new Menu("SlideShow");
playMenuItem = new MenuItem("Toggle Play");
playMenuItem.setShortcut(new MenuShortcut (KeyEvent.VK_P,false));
slideShowMenu.add(playMenuItem);
backMenuItem = new MenuItem("Back");
backMenuItem.setShortcut(new MenuShortcut (KeyEvent.VK_OPEN_BRACKET,false));
slideShowMenu.add(backMenuItem);
forwardMenuItem = new MenuItem("Forward"); forwardMenuItem.setShortcut(new MenuShortcut (KeyEvent.VK_CLOSE_BRACKET,false)); slideShowMenu.add(forwardMenuItem);
menuBar1.add(slideShowMenu);

The code above and the image of the menu the code represents should be the same. Yet, if you look carefully, you will notice a mistake in the image. Can you find it?

optionsmenuWe create a new SlideShow menu and a new Toggle Play item. We assign Command-P as the shortcut, and add the menu item to the menu. Next we create a Back and a Forward item with shortcut keys and add them to the menu. Finally, we add the Slideshow menu to the menu bar.


optionsMenu = new Menu("Options");
controlsMenuItem = new MenuItem("Toggle Controls");
optionsMenu.add(controlsMenuItem);
fullScreenMenuItem = new MenuItem("Toggle Full Screen");
optionsMenu.add(fullScreenMenuItem);
menuBar1.add(optionsMenu);
setMenuBar(menuBar1);

For the options menu, we create a new menu and the appropriate menu items. We do not assign command keys here because these items should not need be used frequently. Lastly, we call setMenuBar( ) which changes the main menu bar to the one we just created.

Now we are ready for the last step in creating our constructor— registering our application event listeners.

Back to top

Step 8 - Registering our Application Event Listeners

Our application needs to install a number of event listeners in order to respond appropriately when events are fired. Not only do we need to listen to our controller and respond when it fires action events, but we also have to listen to all of our menu items so that we can respond when these items are selected from the menus.

    fullScreenMenuItem = new MenuItem("Toggle Full Screen");
    optionsMenu.add(fullScreenMenuItem);
    menuBar1.add(optionsMenu);
    setMenuBar(menuBar1);
				
    //REGISTER_LISTENERS
    //Register ActionListeners with the menu items and 
    //the controller.
    //Insert "SlideShow register listeners"

Locate the SlideShow register listeners clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    fullScreenMenuItem = new MenuItem("Toggle Full Screen");
    optionsMenu.add(fullScreenMenuItem);
    menuBar1.add(optionsMenu);
    setMenuBar(menuBar1);
			
    //REGISTER_LISTENERS
    //Register ActionListeners with the menu items and 
    //the controller.
    //Insert "SlideShow register listeners"
    Action aAction = new Action( );
    openMenuItem.addActionListener(aAction);
    quitMenuItem.addActionListener(aAction);
    controlsMenuItem.addActionListener(aAction);
    fullScreenMenuItem.addActionListener(aAction);
    playMenuItem.addActionListener(aAction);
    backMenuItem.addActionListener(aAction);
    forwardMenuItem.addActionListener(aAction);
    controls.addActionListener(aAction);
}

This system of registering listeners for handling ActionEvents should be fairly familiar by now. As a result, we will talk about this code at a fairly high level. If you are having difficulty understanding, refer to our listener registration code in a previous class (such as in Controller.java).

First, we instantiate an instance of our inner class (that we will define later in Step 19 - Creating an Inner Class to Handle Action Events) that we will use to handle these event. Then we add an action listener to each class we want to be able to respond to. It is important to note that we register a listener with each menu item as well as our controller object.

Next, we will look at threading in our application and write our main thread class.

Back to top

Step 9 - Implementing the SlideShow Threading Model

In our application, we use a thread to handle the automatic advancement of frames in our slideshow. By using threading for timing and displaying subsequent images, we insure that the user interface of our application remains responsive.

We need to skip down in the source file a bit past the togglePlaying( ) method to get to the definition of our inner thread class.

public void togglePlaying( )
{
    //Handle starting and stopping the automatic progression of 
    //the show.
    //Insert "SlideShow togglePlaying"
}
//Inner class to implement our automatic progression of 
//the show.
//Insert "SlideShow PlayRunnable"

Locate the SlideShow PlayRunnable clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

public void togglePlaying( )
{
    //Handle starting and stopping the automatic progression of 
    //the show.
    //Insert "SlideShow togglePlaying"
}

//Inner class to implement our automatic progression of //the show. //Insert "SlideShow PlayRunnable"
class PlayRunnable implements Runnable
{
    public Boolean isRun = true;
		
    public void run( )
    {
        while (isRun)
        {
            oneStep(true);
				
            try
            {
                Thread.sleep(SLEEP_DELAY);
            }
            catch (InterruptedException exc) { }
         }
     }
}

This code creates an inner class that implements the Runnable interface. The runnable interface specifies an API for simple thread classes that have a single method, the run( ) method where the main work of the thread is performed. When the thread is started, the run method is called. Once execution of the run method is completed, the thread is no longer running. The thread still exists, but is no longer alive. We want our thread to continue running as long as we are in play mode. As a result, we have a while loop in our run method that executes as long as isRun is set to true.

Let’s study the code in detail. First, we declare our inner class that implements the Runnable interface. Secondly, we declare a public Boolean data member is run that we initialize to true. We make this data member public, so we can access it from our application class. Next, we declare our run method. In the body, we call oneStep( ) which displays the next image in the slide show. (We will look at this method in detail in Step 11 - Implementing oneStep( )). Lastly, we try to sleep the thread (or perform no operations for our delay interval which we defined to be one second. If for some reason, we can’t sleep our thread because of a InterruptedException, we silently catch the exception and continue our loop.

Now we will go back and define our togglePlaying( ) method.

Back to top

Step 10 - Implementing togglePlaying( )

The togglePlaying( ) method is called when the user clicks on the play/pause button of the controller. If the application is in play mode, we need to stop playing by stopping the thread. If the application is in pause mode, we need to create a new PlayRunnable thread and start it.

Skip back in the source just above the PlayRunnable inner class we just created.

     forwardMenuItem.addActionListener(aAction);
     controls.addActionListener(aAction);
 }

/**
 * Starts or stops cycling forward through the list of image 
 * files to display.
 */
 public void togglePlaying( )	
 {			
     //Handle starting and stopping the automatic progression 
     //of the show.
     //Insert "SlideShow togglePlaying"

Locate the SlideShow togglePlaying clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

     forwardMenuItem.addActionListener(aAction);
     controls.addActionListener(aAction);
 }

/**
 * Starts or stops cycling forward through the list of image 
 * files to display.
 */
 public void togglePlaying( )
 {
     //Handle starting and stopping the automatic progression of 
     //the show.
     //Insert "SlideShow togglePlaying"
     if (isPlaying)
     {
         if (playRunnable != null)
             playRunnable.isRun = false;
         isPlaying = false;
     }
     else
     {
         if (thread == null || !thread.isAlive( ))
         {
             if (playRunnable != null)
                 playRunnable.isRun = false;
					
             playRunnable = new PlayRunnable( );
             thread = new Thread(playRunnable);
             thread.start( );
             isPlaying = true;
         }
     }
}

This code for togglePlaying( ) is fairly straightforward. We check the isPlaying variable (which is initially set to false). If the variable is true, meaning we are in play mode, we need to stop playing. To do so, we check to make sure our playRunnable thread exists (i.e., is non-null). If it has been created, we set the isRun data member of the thread to false. This will cause the while loop in our thread to stop executing, which causes our thread to stop. Lastly, we set our application data member isPlaying to false since we are now stopped.

If isPlaying is false when we enter this function, we are stopped and want to toggle our state to the play mode. Therefore, we check to see if our thread is null (which will be the case if we are entering togglePlay for the first time), and also check to make sure that the thread is not alive by calling thread.isAlive( ). If the run( ) method of our playRunnable thread is executing, isAlive( ) will return true. Otherwise, it will return false. What we are doing here is checking to see if our thread exists and if it does, make sure that it is not alive. If the thread is alive and non-null, we set the isRun variable to false, which will cause the thread to die. Now that we are assured that our thread is no longer running, we create a new thread and assign it to our data member and pass it our new runnable object. Lastly, we start the thread and set isPlaying to true.

Now that we have our threading set up, let’s look at our oneStep( ) method where we will add code to handle slide advancement.

Back to top

Step 11 - Implementing the oneStep( ) method

Our oneStep( ) method is called from the playRunnable object running in our thread. It is responsible for advancing to the next slide in the image list and displaying the image. If we reach the end of the image list, it loops to the first slide. This method is also going to be called in response to clicking the forward and backwards button in the controller. As a result, our routine takes a Boolean parameter that specifies whether we are advancing, or going backwards. A value of true means that we are going forward, while a value of false means that we are stepping backwards to the previous image. Let’s go down past our playRunnable inner class to the declaration for our oneStep( ) method.

             try
             {
                 Thread.sleep(SLEEP_DELAY);
             }
             catch (InterruptedException exc) { }
          }
      }
 }

/** * Steps the slide show forward or backwards by one image. * @param if true, step forward, if false, step backward. */
public void oneStep(Boolean isForward) {
     //Handle stepping forward or backward in the list of 
     //image files, load the image, and repainting.
     //Insert "SlideShow oneStep"

Locate the SlideShow oneStep clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

             try
             {
                 Thread.sleep(SLEEP_DELAY);
             }
             catch (InterruptedException exc) { }
          }
      }
 }

/** * Steps the slide show forward or backwards by one image. * @param if true, step forward, if false, step backward. */
public void oneStep(Boolean isForward) { //Handle stepping forward or backward in the list of //image files, load the image, and repainting. //Insert "SlideShow oneStep"
     int size = files.size( );

     if (size > 0)
     {
         if (isForward)
         {
             currentIndex++;
             if (currentIndex >= size)
                 currentIndex = 0;
         }
         else
         {
             currentIndex--;
             if (currentIndex < 0)
                 currentIndex = size - 1;
         }
			
         File file = (File)files.elementAt(currentIndex);
         if (file != null)
         {
             Image image = Misc.loadImage(file.getPath( ), this, 
                           false);
             if (image != null)
             {
                 if (currentImage != null)
                     currentImage.flush( );
                 currentImage = image;
                 repaint( );
             }
         }
     }
 }

This looks like a lot of code, but it is not as complicated as it may seem. Our first priority is to get the number of images that will be displayed as part of our slideShow. This is accomplished by checking our vector of image files and retrieving the size (the number of elements in the vector). We cache this in a local variable because we will need this number throughout this function.

The next line checks to see if we have more than zero images. If we don’t, then we return, since the concept of going to the next image is meaningless if there are no images. Otherwise, we check our Boolean parameter to determine if we need to step forward or backwards. If we are going forwards (if isForward is true), we increment our index variable. Then if the index exceeds our image capacity (which means that we were on the last image in our show) we set the index to the beginning by setting it to 0.

Otherwise (if we are going backwards), we do a similar thing but we decrement our index and check to see if we are at the first image (index zero) and wrap to the last image if that is the case.

Once we have determined which image will be displayed next, we retrieve a file reference from the vector of files and store it in a local variable. After checking to make sure that the file is not null, we load the image, flush the old image, and set the current image to be the image we just loaded.

Finally, we call repaint( ) on our window so that our paint( ) method is called and our new image is drawn in our window. We will examine paint( ) in detail in Step 16 - Implementing the paint( ) method, but for now, all you need to know is that the paint method draws the image to the screen. Now lets look at our code for the toggleFullScreen( ) method.

Back to top

Step 12 - Implementing the toggleFullScreen( ) method

When the user selects Toggle Fullscreen from the Options menu, it calls the toggleFullScreen( ) method which is responsible for changing the full-screen mode.

/**
 * Handles sizing of the window to utilize the full screen size, 
 * or to use the size of the image.
 */
 public void toggleFullScreen( )
 {
     //Handle toggling the frame window size between the image 
     //size, screen size, and full screen.
     //Insert "SlideShow toggleFullScreen"

Locate the SlideShow toggleFullScreen clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

/**
 * Handles sizing of the window to utilize the full screen size, 
 * or to use the size of the image.
 */
 public void toggleFullScreen( )
 {
     //Handle toggling the frame window size between the image 
     //size, screen size, and full screen.
     //Insert "SlideShow toggleFullScreen"
     Dimension screenSize = 
          Toolkit.getDefaultToolkit( ).getScreenSize( );
		
     if (isFullScreen)
     {
         int width = WIDTH;
         int height = HEIGHT;

         if (currentImage != null)
         {
             width = currentImage.getWidth(this);
             height = currentImage.getHeight(this);
				
             //Make sure the window fits on the screen
             width = Math.min(width, screenSize.width);
             height = Math.min(height, screenSize.height);
         }

         setLocation((screenSize.width - width) / 2, 
                     (screenSize.height - height) / 2);
         setSize(width, height);

         isFullScreen = false;
     }
     else
     {
         int top = 21;
         int sides = 5;
         setBounds(-sides, -top, screenSize.width + 2 * sides, 
                    screenSize.height + top + sides);
         isFullScreen = true;
     }
 }

This method looks bad, but it is mostly just math. After we get the screen size from the toolkit, there are two main branches of execution. The first occurs if we are in full-screen mode and want to go to normal mode, and the second is if we are in normal mode and want to change to full screen. Let’s look at the first case.

Since we are in full screen and want to go to normal mode, we first set up two variables initialized to our default width and height. These default values will be used if for some reason our image is null. Next, if the image is not null, we retrieve its width and height and store them in our local variables. Then we assign width and height to the smaller of either the screen size for that dimension, or the image size. This insures that if the image is larger than the screen, that we make our window the size of the screen instead. If the image is smaller, we use the image size. This is accomplished by our judicious use of the min function that is part of the standard Math package.

Next, we set the position of the window so that it is centered on the screen, and set the width and height of the window. Finally we set the isFullScreen variable to false.

In the case where we are in normal display mode and want to go full screen, we set the bounds of the window to the screen size and then set isFullScreen to true. Note that since our window has a frame and a title bar, we had to correct for the height and width of this border. That is where the top and sides value comes in. We want to make sure that we move the window 21 pixels above the top of the screen so that we don’t see the title bar. We do the same with the edges to ensure that the edge border is not visible.

Now it’s time for the ever-popular toggleControls( ).

Back to top

Step 13 - Implementing toggleControls( )

The toggleControls method is called from the Toggle Controls menu item of the Options menu. It will show the controller window if it is hidden, and hide the window if it is visible.

/**
 * Shows or hides the control window.
 */
 public void toggleControls( )
 {
     //Handle toggling the visibility of the controller
     //Insert "SlideShow toggleControls"

Locate the SlideShow toggleFullScreen clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

/**
 * Shows or hides the control window.
 */
 public void toggleControls( )
 {
     //Handle toggling the visibility of the controller
     //Insert "SlideShow toggleControls"
     if (controls != null)
         controls.setVisible(!controls.isVisible( ));
 }

What we do here is first make sure that our controller is not null. If it is, then all bets are off and we shouldn’t do anything. Otherwise, we show the controller if it is hidden, and hide the controller if it is visible by setting the visibility of the object to the logical opposite of its current visibility state. Pretty slick.

For the next step, we will implement our quit handler.

Back to top

Step 14 - Implementing doOnQuit( )

Our doOnQuit( ) method is called when the user selects the Quit item from the File menu or we receive a QuitApplication AppleEvent. More on this in Step 18 - Registering Special MRJ Handlers.

/**
 * Gets called when the user chooses the Quit menu item, or 
 * when the application receives a quit message from the Finder 
 * (or other app).
 */
 protected void doOnQuit( )
 {
     //Handle cleaning up, and quit.
     //Insert "SlideShow doOnQuit"
     //Do any clean up here.

Locate the SlideShow doOnQuit clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

/**
 * Gets called when the user chooses the Quit menu item, or 
 * when the application receives a quit message from the Finder 
 * (or other app).
 */
 protected void doOnQuit( )
 {
     //Handle cleaning up, and quit.
     //Insert "SlideShow doOnQuit"
     //Do any clean up here.
     //Exit with success.
     System.exit(0);
 }

When we receive a quit message, we call java.lang.System.exit( ). This routine is very similar to the C call, ExitToShell( ). It shuts down the application and terminates the Virtual machine. The parameter is the message that would be returned on a command-line based system. We pass zero as the parameter to indicate that we exited because the user quit, not because of an error condition.

Now we will implement doAbout( ) which displays our about dialog box.

Back to top

Step 15 - Implementing doAbout( )

aboutThe doAbout routine is called when the user selects About SlideShow from the Apple menu. It displays our About dialog (see image above) by instantiating our AboutDialog class. We use the MRJ toolkit to place the About item on the Apple menu. Normally, the Apple menu would not be accessible in Java (since it is platform specific), but we use some Macintosh-specific routines to give our users a more Mac-like experience. We will discuss the steps that we took to do this later in Step 18 - Registering Special MRJ Handlers.

/**
 * Gets called when the user selects the about menu item in 
 * the Apple Menu.
 */
 protected void doAbout( )
 {
     //Handle displaying about information here
     //Insert "SlideShow doAbout"

Locate the SlideShow doAbout clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

/**
 * Gets called when the user selects the about menu item in 
 * the Apple Menu.
 */
 protected void doAbout( )
 {    
     //Handle displaying about information here
     //Insert "SlideShow doAbout"
     if (aboutDialog1 == null)
         aboutDialog1 = new AboutDialog(this, true);
     aboutDialog1.setVisible(true);
 }

The doAbout method checks to see if the dialog is null (it will be if it has not previously been displayed). If the dialog is null, we create a new dialog passing our window as the parent frame. Then we display the dialog by calling setVisible( ) with a true argument.

Roll up your sleeves, because it is time to implement paint.

Back to top

Step 16 - Implementing the paint( ) method

Paint is responsible for the drawing of our window. It handles drawing of the images as well as centering and scaling within the window. This extra work adds a bit of complexity, but makes the application much nicer and polished.

public void paint(Graphics g)
{
    //Handle scaling and drawing the image to fit in the 
    //frame content area.
    //Insert "SlideShow paint"

Locate the SlideShow paint clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

public void paint(Graphics g)
{
//Handle scaling and drawing the image to fit in the //frame content area. //Insert "SlideShow paint"
     if (currentImage != null)
     {
         Dimension s = getSize( );
         int iWidth = currentImage.getWidth(this);
         int iHeight = currentImage.getHeight(this);

         int scaleWidth = iWidth;
         int scaleHeight = iHeight;
			
         int wDelta = s.width - iWidth;
         int hDelta = s.height - iHeight;
			
         if (wDelta > 0 && hDelta > 0)
         {
             //The image fits, just draw it.
             g.drawImage(currentImage, (s.width - iWidth) / 2, 
                  (s.height - iHeight) / 2, this);
         }
         else
         {
             //The image doesn’t fit. We need to scale it 
             //down to fit.

             float ratio = 1;
				
             if (wDelta < hDelta)
             {
                 if (iWidth > 0)
                 {
                     //width needs to be scaled to fit
                     ratio = s.width / (float)iWidth;
                 }
             }
             else
             {
                 if (iHeight > 0)
                 {
                     //height needs to be scaled to fit
                     ratio = s.height / (float)iHeight;
                 }
             }
				
             scaleWidth = (int)(iWidth * ratio);
             scaleHeight = (int)(iHeight * ratio);

             g.drawImage(currentImage, 
                    (s.width - scaleWidth) / 2, 
                    (s.height - scaleHeight) / 2, 
                     scaleWidth, scaleHeight, this);
        }
    }
}

Whoa Nellie! That’s some funky math. Don’t worry about it. Understanding the algorithm isn’t as important as understanding what the paint method does

First, we make sure we have a non-null image. If the image is null, there is nothing to draw, and we are done. If the image is not null, we get the size of the image and check to see if it fits inside our window. If the image fits, we draw it centered within the window. If it does not fit, we scale the image to fit and then draw the scaled image in our window.

The main drawing task is done by drawImage( ). When we are called, we are passed in a graphic context to draw into, and we call java.awt.Graphics.drawImage( ) from this context. We pass the image object to draw, the X location, Y location, width, height, and ourselves as the observer. The observer is an object that will receive notification when the drawing of the image is complete. We pass ourselves as the observer, even though we don’t do anything special when we are notified of completion.

Next, we will implement setVisible( ).

Back to top

Step 17 - Implementing setVisible( )

We override the setVisible( ) method to ensure that if the main window is made visible, the controller is made visible as well. Conversely, if the main window is hidden, we want to hide the controller.

public void setVisible(Boolean b)
{
    //Make sure the controls are visible only when the 
    //frame is visible.
    //Insert "SlideShow setVisible"

Locate the SlideShow setVisible clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

public void setVisible(Boolean b)
{
//Make sure the controls are visible only when the //frame is visible. //Insert "SlideShow setVisible"
    super.setVisible(b);
		
    if (controls != null)
        controls.setVisible(b);
}

Since we are adding functionality to our setVisible method override, we call setVisible( ) from our base class (Frame) to insure that we inherit the default behavior. Next, we check to see if the controls are null. If they aren’t we set the visibility of the controller to match the visibility of our window.

In the next step, we will register handlers to add Macintosh-specific functionality to our application.

Back to top

Step 18 - Registering Special MRJ Handlers

Users expect their Macintosh applications to behave in a consistent way and are unwilling to accept “But this is a Java program” as an excuse for loss of functionality. As a result, we add three specific handlers for our application to add functionality, an open document handler, an about handler, and a quit handler. The open document handler allows us to receive notification when documents are dropped on our application icon (in the case of a JBound application). When this occurs, the Finder sends an OpenDocument event to our application which will call our registered handler. In our case, any image files dropped on our application icon should be added to our image list.

The second handler, the about handler, notifies us when the user chooses the about item in the Apple Menu. There is also some additional work in the form of a Macintosh resource file that needs to be completed in order for this to look just right. We will cover this step in Making a Double-clickable Application

The third and final handler, the quit handler, allows the application to respond to quit events from the Finder. A quit event is generated when the user selects Shut Down from the Special Menu in the Finder. All running applications are notified via the Quit AppleEvent. This gives the user a chance to save any open documents, clean up, etc., before the system shuts down. If we did not handle this, and our program was running, the computer would not be able to shut down because the Finder would be waiting for our application to terminate.

protected void registerMRJHandlers( )
{
    //Register MRJ handlers for open, about and quit.
    //Insert "SlideShow registerMRJHandlers"

Locate the SlideShow registerMRJHandlers clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

protected void registerMRJHandlers( )
{
//Register MRJ handlers for open, about and quit. //Insert "SlideShow registerMRJHandlers"
    MRJI IMRJI = new MRJI( );
    MRJApplicationUtils.registerOpenDocumentHandler(IMRJI);
    MRJApplicationUtils.registerQuitHandler(IMRJI);
    MRJApplicationUtils.registerAboutHandler(IMRJI);
}

This code may look a little strange at first, but it will make more sense if we examine the next code block in tandem.

    MRJApplicationUtils.registerAboutHandler(IMRJI);
}
//Inner class defining the MRJ Interface
//Insert "SlideShow MRJI"

Locate the SlideShow MRJI clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    MRJApplicationUtils.registerAboutHandler(IMRJI);
}

//Inner class defining the MRJ Interface
//Insert "SlideShow MRJI"
class MRJI implements MRJOpenDocumentHandler, MRJQuitHandler, 
                      MRJAboutHandler
{
    /**
     * This gets called by MRJ for each file/folder dropped 
     * onto the application.
     * If the file is a directory, this recurses through the 
     * directory structure collecting image files.
     * @param the file to add to the list of image files to 
     * display, or the File object to recurse to look for image 
     * files to add to the list.
     */
     public void handleOpenFile(File file)
     {
         if(file != null)
         {
             if (file.isDirectory( ))
             {
                  String directory = file.getPath( );
                  if (!directory.endsWith("/")) 
                       directory += "/";
	
                  String[] fileList = file.list( );	
                  for (int fileInd = 0; 
                           fileInd < fileList.length; 
                           fileInd++)
                  {
                      this.handleOpenFile(new File(directory + 
                          fileList[fileInd]));
                  }
              }
              else
              {
                  String upperCaseName = 
                         file.getName( ).toUpperCase( );
                  if (upperCaseName.endsWith(".JPG") ||  
                      upperCaseName.endsWith(".JPEG") ||
                     upperCaseName.endsWith(".gif"))
                  {
                      files.addElement(file);
                  }
               }
            }
        }
		
       /**
        * This gets called when the application receives a 
        * quit event
        */
        public void handleQuit( )
        {
            doOnQuit( );
        }
		
       /**
        * This gets called when the About menu item in the 
        * Apple Menu is selected.
        */
        public void handleAbout( )
        {
            doAbout( );
        }
    }	
}

OK. Now that we have the inner class and the handler registration routine, we can look at both as a single unit, starting with the inner class.

We declare our inner class:

class MRJI implements MRJOpenDocumentHandler, MRJQuitHandler, MRJAboutHandler

We need to implement three interfaces from the MRJApplicationUtilities package so that we can handle specific custom event types. By implementing these three interfaces, we provide three methods can be called by the MRJ in response to specific events: handleOpenFile( ), handleQuit( ), and handleAbout( ).

This organization is exactly the same as the one we use to handle events such as the ActionEvent. We make an inner class that implements an interface specifying the signature of the class to be called. In this case, instead of implementing actionPerformed( ), we are implementing three custom handlers. These handlers each call a routine to respond the particular event.

Let’s look at the code in detail starting with our MRJI inner class:

class MRJI implements MRJOpenDocumentHandler, MRJQuitHandler, MRJAboutHandler

Our class implements the MRJOpenDocumentHandler interface as well as the MRJQuitHandler and MRJAboutHandler interfaces. We need to implement these interfaces in order to handle these specific event types.

Let’s start with the open document handler:

public void handleOpenFile(File file) 
{ 
    if(file != null) 
if (file.isDirectory( )) {

Our handleOpenFile( ) method takes a file object as the parameter. This is the file that the user dropped on our application. If multiple files were dropped, handleOpenFile( ) will get called once for each individual file. We first check to see if the file is null. If it is, we don’t do anything. Otherwise, we check to see if our file is a directory. If it is, we need to iterate through all of the files in that directory. If it is not a directory, then it is an individual file and we can deal with that directly.

Let’s look at the directory case:

    String directory = file.getPath( );
if (!directory.endsWith("/")) directory += "/"; String[] fileList = file.list( ); for (int fileInd = 0; fileInd < fileList.length; fileInd++) { this.handleOpenFile(new File(directory + fileList[fileInd]));
} }


Our first task is to get the path of the directory and store it in a local variable as a string. We accomplish this by calling getPath( ) from the java.io.File class. Next, we append a slash ("/") character if the path does not end with one. Then we create an array of files that contains the list of files in the directory we are passed. Lastly, we loop through this array and call ourselves recursively with each file in the directory.

If we were not passed a directory, we execute the following code:

else          
{ 
    String upperCaseName = file.getName( ).toUpperCase( ); 
    if (upperCaseName.endsWith(".JPG") 
|| upperCaseName.endsWith(".JPEG")
|| upperCaseName.endsWith(".gif"))
{
files.addElement(file);
}
}

We get the name of the file, convert it to uppercase, and store in a temporary variable. Then, we look at the file name, and only add it to our list of files if it ends with an appropriate file extension.

The other two handlers are very simple.

public void handleQuit( ) 
{ 
   doOnQuit( ); 
} 

public void handleAbout( ) 
{ 
    doAbout( ); 
} 

Both simply call the methods that we previously implemented that do the real work. Now let’s look at our registration function:

protected void registerMRJHandlers( ) 
{
    MRJI IMRJI = new MRJI( ); 
    MRJApplicationUtils.registerOpenDocumentHandler(IMRJI);          
    MRJApplicationUtils.registerQuitHandler(IMRJI); 
    MRJApplicationUtils.registerAboutHandler(IMRJI);
}

This method which we call from our constructor registers our MRJI class with MRJ to be used as the handler for the open, quit, and about events. In our method, we create a new instance of our inner class handler. Then we call registration functions from the MRJApplicationUtils package with our inner class as a parameter. These methods are documented in the Adobe Acrobat file "About MRJ Toolkit," which is distributed as part of the MRJ SDK,

Next, we will begin to create our event handling architecture starting with the implementation of an inner class for handling ActionEvents.

Back to top

Step 19 - Creating a Inner Class to Handle ActionEvents

In this step, we will be creating our inner class that will be used to handle action events from the menus, and the controller.

    public void handleAbout( )
    {
        doAbout( );
    }
}	
//Inner class for handling ActionEvents
//Insert "SlideShow Action"

Locate the SlideShow registerMRJHandlers clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

    public void handleAbout( )
    {
        doAbout( );
    }
}	

//Inner class for handling ActionEvents
//Insert "SlideShow Action"
class Action implements ActionListener
{
    public void actionPerformed(ActionEvent event)
    {
        Object object = event.getSource( );
        if (object == openMenuItem)
            openMenuItem_ActionPerformed(event);
        else if (object == quitMenuItem)
            quitMenuItem_ActionEvent(event);
        else if (object == controlsMenuItem)
            controlsMenuItem_ActionPerformed(event);
        else if (object == fullScreenMenuItem)
            fullScreenMenuItem_ActionPerformed(event);
        else if (object == playMenuItem)
            playMenuItem_ActionPerformed(event);
        else if (object == backMenuItem)
            backMenuItem_ActionPerformed(event);
        else if (object == forwardMenuItem)
            forwardMenuItem_ActionPerformed(event);
        else if (object == controls)
            controls_ActionPerformed(event);
    }
}

Once again, this code should be familiar by now. Just like we have done in numerous other classes, our inner class implements the ActionListener interface. We override the actionPerformed method so that when we receive an action event, we can process it. The only difference is that we are handling events many different object types. Our class is a big if-then-else statement that compares the source of the event with a list of object types and then calls a specific method to handle the event if the types match. Most of these handlers are for menu items, except the last item, which handles events from the controller.

Let’s look at the implementation of the individual action performed methods for each class.

Back to top

Step 20 - Responding to ActionEvents

In our final step, we will implement all of the methods that handle the action events dispatched from our inner class event handler.

        else if (object == controls)
            controls_ActionPerformed(event);
    }
}
//Routines for handling the various ActionEvents
//Insert "SlideShow Action Management"

Locate the SlideShow Action Management clipping in the SlideShow folder and drag it directly below the last line of code shown above. Your code should now look like this:

        else if (object == controls)
            controls_ActionPerformed(event);
    }
}

//Routines for handling the various ActionEvents
//Insert "SlideShow Action Management"
void openMenuItem_ActionPerformed(ActionEvent event)
{
    //Present the load file dialog.
    openFileDialog1.setVisible(true);
		
    //Make sure a valid value is returned (user could cancel).
    String resultPath = openFileDialog1.getDirectory( );
    String resultFile = openFileDialog1.getFile( );
    if(resultPath != null && resultPath.length( ) != 0 
       && resultFile != null && resultFile.length( ) != 0)
    {
        //Construct a File object from the information from the
        //dialog.
        File file = new File(resultPath + resultFile);
        if(file != null)
        {
            //Add the file to our list of image files.
            files.addElement(file);
		
            //Load the image from the file, and display it as 
            //our current image.
            Image image = Misc.loadImage(file.getPath( ), this, 
false); if (image != null) { if (currentImage != null) currentImage.flush( ); currentImage = image; currentIndex = files.size( ) - 1; repaint( ); } } } } void quitMenuItem_ActionEvent(ActionEvent event) { doOnQuit( ); } void controlsMenuItem_ActionPerformed(ActionEvent event) { toggleControls( ); } void fullScreenMenuItem_ActionPerformed(ActionEvent event) { toggleFullScreen( ); } void playMenuItem_ActionPerformed(ActionEvent event) { togglePlaying( ); controls.setPlayState(!isPlaying); } void backMenuItem_ActionPerformed(ActionEvent event) { oneStep(false); } void forwardMenuItem_ActionPerformed(ActionEvent event) { oneStep(true); } void controls_ActionPerformed(ActionEvent event) { String command = event.getActionCommand( ); if (command.equals(Controller.BACKWARD_COMMAND)) oneStep(false); else if (command.equals(Controller.FORWARD_COMMAND)) oneStep(true); else if (command.equals(Controller.PLAY_COMMAND)) togglePlaying( ); else if (command.equals(Controller.PAUSE_COMMAND)) togglePlaying( ); } }

The first method openMenuItem_ActionPerformed( ) is called when the open item on the File menu is chosen.

The first step is to display our open file dialog.

openFileDialog1.setVisible(true);

This call does not return until the user clicks on either the Choose button (OK for systems that do not have Navigation Services installed) or Cancel.

Once the user exits the dialog, we get the file from the dialog and test it to see if it is valid:

String resultPath = openFileDialog1.getDirectory( );
String resultFile = openFileDialog1.getFile( );
if
(resultPath != null && resultPath.length( ) != 0 && resultFile != null && resultFile.length( ) != 0)

We get the file and the directory from the file dialog as a string which we cache in temporary variables. Then we check to make sure that the strings are not empty or null. If the user clicked the Cancel button, these would be null, and we would then not attempt to open the file.

Otherwise, we create a new file object and check to make sure it is valid:

File file = new File(resultPath + resultFile);

if(file != null) { //Add the file to our list of image files.  
files.addElement(file);

Our new file object takes a string argument which is the concatenation of the directory and the file. We make sure the file is not null, and if it is valid, add the file to our internal file list.

Finally, we load the image from the file and display it:

Image image = Misc.loadImage(file.getPath( ), this, false); 
if (image != null) 
{ if (currentImage != null) currentImage.flush( ); currentImage = image; currentIndex = files.size( ) - 1;
repaint( ); }

We load the image using our miscellaneous class and store a local reference. If we have a current image in memory (i.e., the image reference is not null), we flush it from memory. This removes the object from memory immediately. We could set the variable to null and wait for the garbage collector, but these images could be of potentially very large size, so we want to free up the memory immediately.

We set the current image to our newly loaded image, and set the current index variable to be to location of our picture which is placed at the end of the vector. We use files.size( ) = -1 because the vector is zero based. To get the last item, we need to subtract one. We then call repaint so that our new image is drawn.

Our remaining handlers are much simpler. The next, quitMenuItem_ActionEvent( ), is called when the user selects Quit from the File menu.

void quitMenuItem_ActionEvent(ActionEvent event) 
{ 
    doOnQuit( ); 
} 

This method simply cause the doOnQuit( ) method we previously implemented. The method controlsMenuItem_ActionPerformed( ) is called when Toggle Controls is selected from the Options menu.

void controlsMenuItem_ActionPerformed(ActionEvent event) 
{ 
    toggleControls( ); 
} 

This method simply calls toggleControls( ). Next is fullScreenMenuItem_ActionPerformed( ) which is called when the user selects Toggle Fullscreen from the options menu.

void fullScreenMenuItem_ActionPerformed(ActionEvent event) 
{ 
    toggleFullScreen( );          
} 

This method is simple as well. It calls toggleFullScreen( ). PlayMenuItem_ActionPerformed( ) is called when the Toggle Play item is selected from the SlideShow menu.

void playMenuItem_ActionPerformed(ActionEvent event) 
{ 
    togglePlaying( ); 
    controls.setPlayState(!isPlaying);
} 

This method calls togglePlaying( ) and then notifies the controller that the play state has changed by calling setPlayState from the controller object. The method backMenuItem_ActionPerformed( ) is called when the Back menu item is selected from the SlideShow menu.

void backMenuItem_ActionPerformed(ActionEvent event) 
{ 
    oneStep(false);
} 

Here, we call oneStep( ) with false as the parameter. The value of false tells us to go backwards instead of forwards. Our next method, forwardMenuItem_ActionPerformed( ) is very similar. It is called when the Forward menu item is selected from the SlideShow menu.

void forwardMenuItem_ActionPerformed(ActionEvent event) 
{ 
    oneStep(true); 
} 

This method does the same thing as the back method, but we pass true to oneStep( ) in order to go forward instead of backward. Our last method, controls_ActionPerformed( ), is called in response to any button in the controller being pressed.

void controls_ActionPerformed(ActionEvent event) 
{ 
    String command = event.getActionCommand( ); 
    if (command.equals(Controller.BACKWARD_COMMAND))
        oneStep(false); 
    else if (command.equals(Controller.FORWARD_COMMAND))
        oneStep(true); 
    else if (command.equals(Controller.PLAY_COMMAND))
        togglePlaying( ); 
    else if (command.equals(Controller.PAUSE_COMMAND)) 
        togglePlaying( ); 
} 

This method is a bit more complex because it has to handle the events from the controller and respond appropriately depending on the individual button that sent the event. First, we set up a string variable to store the action command of the button.

Next, we compare the command to a number of pre-defined strings in the controller. If there is a match, we respond appropriately. For example, if the command tells us that the even is coming from the backwards button, we call oneStep( ) with a false argument. This is the same as what would happen if the user chose the Back item from the SlideShow menu. However, we are responding to a message from the controller instead of a message from a menu item.

Back to top

Summary

We did a lot of work in this class. We started out setting up the application menus and initializing class data members. Then we initialized the application state in the main routine and created the controls. This completes all of the methods in the main application class, SlideShow. We registered our listeners and implemented the threading model responsible for advancing images in the slide show. Next, we implemented the methods called in response to the various menu items and buttons, and implemented some custom MRJ handlers for responding to AppleEvents. Finally, we implemented our main application event handling routines.

In our last file, we implement the ImageNameFilter used by the open file dialog in this class. To return to the main tutorial file, click here.