FileDocCategorySizeDatePackage
PhotoAlbum.javaAPI DocJ2ME MIDP 2.018173Thu Nov 07 12:02:18 GMT 2002example.photoalbum

PhotoAlbum

public class PhotoAlbum extends MIDlet implements CommandListener, Runnable, ItemStateListener
The PhotoAlbum MIDlet provides the commands and screens that implement a simple photograph and animation album. The images and animations to be displayed are configured in the descriptor file with attributes.

Options are provided to to vary the speed of display and the frame style.

Fields Summary
private Command
aboutCommand
The Command object for the About command
private Command
exitCommand
The Command object for the Exit command
private Command
okCommand
The Command object for the Ok command
private Command
optionsCommand
The Command object for the Options command
private Command
backCommand
The Command object for the Back command
private Command
cancelCommand
The Command object for the Cancel command
private Form
progressForm
The Form object for the Progress form
private Gauge
progressGauge
The Gauge object for the Progress gauge
private Form
optionsForm
The Form object for the Options command
private ChoiceGroup
borderChoice
Set of choices for the border styles
private ChoiceGroup
speedChoice
Set of choices for the speeds
private Display
display
The current display for this MIDlet
private PhotoFrame
frame
The PhotoFrame that displays images
private Alert
alert
The Alert for messages
private Vector
imageNames
Contains Strings with the image names
private List
imageList
List of Image titles for user to select
private String
imageName
Name of current image, may be null
private Thread
thread
Current thread loading images, may be null
private final String
optionsName
Name of persistent storage
private RecordStore
optionsStore
Persistent storage for options
Constructors Summary
public PhotoAlbum()
Construct a new PhotoAlbum MIDlet and initialize the base options and main PhotoFrame to be used when the MIDlet is started.


                              
        
        display = Display.getDisplay(this);
        exitCommand = new Command("Exit", Command.EXIT, 1);
	optionsCommand = new Command("Options", Command.SCREEN, 1);
	okCommand = new Command("Ok", Command.OK, 3);
        backCommand = new Command("Back", Command.BACK, 3);
        cancelCommand = new Command("Cancel", Command.CANCEL, 1);
	aboutCommand = new Command("About", Command.HELP, 30);

        frame = new PhotoFrame();
	frame.setStyle(2);
	frame.setSpeed(2);
	frame.addCommand(optionsCommand);
        frame.addCommand(backCommand);
        frame.setCommandListener(this);
        alert = new Alert("Warning");
        setupImageList();
    
Methods Summary
voidcloseOptions()
Close the options store.

        if (optionsStore != null) {
            try {
                optionsStore.closeRecordStore();
                optionsStore = null;
            } catch (RecordStoreException ex) {
                alert.setString("Could not close options storage");
                display.setCurrent(alert);
            }
        }
    
public voidcommandAction(Command c, Displayable s)
Respond to commands. Commands are added to each screen as they are created. Each screen uses the PhotoAlbum MIDlet as the CommandListener. All commands are handled here:
  • Select on Image List - display the progress form and start the thread to read in the images.
  • Options - display the options form.
  • Ok on the Options form - returns to the PhotoFrame.
  • Back - display the Image List, deactivating the PhotoFrame.
  • Cancel - display the image List and stop the thread loading images.
  • Exit - images are released and notification is given that the MIDlet has exited.

param
c the command that triggered this callback
param
s the screen that contained the command

        if (c == exitCommand) {
            // Cleanup and notify that the MIDlet has exited
            destroyApp(false);
            notifyDestroyed();
        } else if (c == optionsCommand) {
            // Display the options form
	    display.setCurrent(genOptions());
	} else if (c == okCommand && s == optionsForm) {
            // Return to the PhotoFrame, the option values have already
            // been saved by the item state listener
	    display.setCurrent(frame);
	} else if (c == List.SELECT_COMMAND) {
            // Display the progress screen and
            // start the thread to read the images
            int i = imageList.getSelectedIndex();
            imageName = (String)imageNames.elementAt(i);
	    display.setCurrent(genProgress(imageList.getString(i)));
	    thread = new Thread(this);
            thread.start();
        } else if (c == backCommand) {
            // Display the list of images.
            display.setCurrent(imageList);
        } else if (c == cancelCommand) {
	    // Signal thread to stop and put an alert.
	    thread = null;
            alert.setString("Loading images cancelled.");
            display.setCurrent(alert, imageList);
	} else if (c == aboutCommand) {
	    About.showAbout(display);
	}
    
private ImagecreateImage(java.lang.String name)
Fetch the image. If the name begins with "http:" fetch it with connector.open and http. If it starts with "/" then load it from the resource file.

param
name of the image to load
return
image created
exception
IOException if errors occuring doing loading

        if (name.startsWith("/")) {
            // Load as a resource with Image.createImage
            return Image.createImage(name);
        } else if (name.startsWith("http:")) {
            // Load from a ContentConnection
            HttpConnection c = null;
	    DataInputStream is = null;
            try {
	        c = (HttpConnection)Connector.open(name);
		int status = c.getResponseCode();
		if (status != 200) {
		    throw new IOException("HTTP Response Code = " + status);
		}

                int len = (int)c.getLength();
                String type = c.getType();
		if (!type.equals("image/png")) {
		    throw new IOException("Expecting image, received " + type);
		}

                if (len > 0) {
		    is = c.openDataInputStream();
		    byte[] data = new byte[len];
		    is.readFully(data);
                    return Image.createImage(data, 0, len);
	        } else {
		    throw new IOException("Content length is missing");
	        }
	    } finally {
	        if (is != null)
                    is.close();
                if (c != null)
                    c.close();
            }
        } else {
            throw new IOException("Unsupported media");
        }
    
protected voiddestroyApp(boolean unconditional)
Destroy must cleanup everything not handled by the garbage collector. In this case there is nothing to cleanup. Save the options for the next restart.

param
unconditional true if this MIDlet should always cleanup

        saveOptions();
        frame.reset();          // Discard images cached in the frame.
        saveOptions();
        closeOptions();
    
private ScreengenOptions()
Generate the options form with speed and style choices. Speed choices are stop, slow, medium, and fast. Style choices for borders are none, plain, fancy.

return
the generated options Screen

	if (optionsForm == null) {
	    optionsForm = new Form("Options");
	    optionsForm.addCommand(okCommand);
	    optionsForm.setCommandListener(this);
	    optionsForm.setItemStateListener(this);

	    speedChoice = new ChoiceGroup("Speed", Choice.EXCLUSIVE);
	    speedChoice.append("Stop", null);
	    speedChoice.append("Slow", null);
	    speedChoice.append("Medium", null);
	    speedChoice.append("Fast", null);
	    speedChoice.append("Unlimited", null);
	    speedChoice.setSelectedIndex(frame.getSpeed(), true);
	    optionsForm.append(speedChoice);

	    borderChoice = new ChoiceGroup("Borders", Choice.EXCLUSIVE);
	    borderChoice.append("None", null);
	    borderChoice.append("Plain",  null);
	    borderChoice.append("Fancy", null);
	    borderChoice.setSelectedIndex(frame.getStyle(), true);
	    optionsForm.append(borderChoice);
	}
	return optionsForm;
    
private ScreengenProgress(java.lang.String name)
Generate the options form with image title and progress gauge.

param
name the title of the Image to be loaded.
return
the generated progress screen

	if (progressForm == null) {
	    progressForm = new Form(name);
	    progressForm.addCommand(cancelCommand);
	    progressForm.setCommandListener(this);

	    progressGauge =
		new javax.microedition.lcdui.Gauge("Loading images...",
						   false, 9, 0);

	    progressForm.append(progressGauge);
	} else {
	    progressGauge.setValue(0);
	    progressForm.setTitle(name);
	}
	return progressForm;
    
public voiditemStateChanged(Item item)
Listener for changes to options. The new values are set in the PhotoFrame.

param
item - the item whose value has changed.

	if (item == borderChoice) {
            frame.setStyle(borderChoice.getSelectedIndex());
	} else if (item == speedChoice) {
	    frame.setSpeed(speedChoice.getSelectedIndex());
	}
    
voidopenOptions()
Open the store that holds the saved options. If an error occurs, put up an Alert.

        try {
            optionsStore = RecordStore.openRecordStore(optionsName, true);
        } catch (RecordStoreException ex) {
            alert.setString("Could not access options storage");
            display.setCurrent(alert);
            optionsStore = null;
        }
    
protected voidpauseApp()
Pause is used to release the memory used by Image. When restarted the images will be re-created. Save the options for the next restart.

        saveOptions();
        frame.reset();          // Discard images cached in the frame.
    
voidrestoreOptions()
Restore the options from persistent storage. The options are read from record 1 and set in the frame and if the optionsForm has been created in the respective ChoiceGroups.

        if (optionsStore != null) {
            try {
                byte[] options = optionsStore.getRecord(1);
                if (options.length == 2) {
                    frame.setStyle(options[0]);
                    frame.setSpeed(options[1]);
                    if (optionsForm != null) {
                        borderChoice.setSelectedIndex(options[0], true);
                        speedChoice.setSelectedIndex(options[1], true);
                    }
                    return;         // Return all set
                } 
            } catch (RecordStoreException ex) {
		// Ignore, use normal defaults
            }
        }
    
public voidrun()
The Run method is used to load the images. A form is used to report the progress of loading images and when the loading is complete they are displayed. Any errors that occur are reported using an Alert. Images previously loaded into the PhotoFrame are discarded before loading.

Load images from resource files using Image.createImage. Images may be in resource files or accessed using http: The first image is loaded to determine whether it is a single image or a sequence of images and to make sure it exists. If the name given is the complete name of the image then it is a singleton. Otherwise it is assumed to be a sequence of images with the name as a prefix. Sequence numbers (n) are 0, 1, 2, 3, .... The full resource name is the concatenation of name + n + ".png".

If an OutOfMemoryError occurs the sequence of images is truncated and an alert is used to inform the user. The images loaded are displayed.

see
createImage

	Thread mythread = Thread.currentThread();
        Vector images = new Vector(5);
	/* Free images and resources used by current frame. */
        frame.reset();
        try {			// Catch OutOfMemory Errors
	    try {
                if (imageName.startsWith("testchart:")) {
                    TestChart t = new TestChart(frame.getWidth(),
                                                    frame.getHeight());
                    images = t.generateImages();
                } else {
                    // Try the name supplied for the single image case.
		    images.addElement(createImage(imageName));
                }
	    } catch (IOException ex) {
		try {
		    int namelen = imageName.length();
		    StringBuffer buf = new StringBuffer(namelen + 8);
		    buf.append(imageName);
		    Runtime rt = Runtime.getRuntime();
		    // Try for a sequence of images.
		    for (int i = 0; ; i++) {

			progressGauge.setValue(i % 10);
                        // If cancelled, discard images and return immediately
			if (thread != mythread) {
			    break;
			}

			// locate the next in the series of images.
			buf.setLength(namelen);
			buf.append(i);
			buf.append(".png");
			String name = buf.toString();
			images.addElement(createImage(name));
		    }
		} catch (IOException io_ex) {
		}
	    } catch (SecurityException se_ex) {
		// no-retry, just put up the alert
	    }

            // If cancelled, discard images and return immediately
            if (thread != mythread) {
                return;
            }

            // If any images, setup the images and display them.
	    if (images.size() > 0) {
                frame.setImages(images);
		display.setCurrent(frame);
	    } else {
		// Put up an alert saying image cannot be loaded
		alert.setString("Images could not be loaded.");
		display.setCurrent(alert, imageList);
	    }
	    
	} catch (OutOfMemoryError err) {
	    int size = images.size();
	    if (size > 0) {
		images.setSize(size-1);
	    }

	    // If cancelled, discard images and return immediately
	    if (thread != mythread) {
		return;
	    }

	    alert.setString("Not enough memory for all images.");
            // If no images are loaded, Alert and return to the list
	    // Othersize, Alert and display the ones that were loaded.
	    if (images.size() <= 0) {
 		display.setCurrent(alert, imageList);
	    } else {
                frame.setImages(images);
                display.setCurrent(alert, frame);
            }
        }
    
voidsaveOptions()
Save the options to persistent storage. The options are retrieved ChoiceGroups and stored in Record 1 of the store which is reserved for it. The two options are stored in bytes 0 and 1 of the record.

        if (optionsStore != null) {
            byte[] options = new byte[2];
            options[0] = (byte)frame.getStyle();
            options[1] = (byte)frame.getSpeed();
            try {
                optionsStore.setRecord(1, options, 0, options.length);
            } catch (InvalidRecordIDException ridex) {
                // Record 1 did not exist, create a new record (Should be 1)
                try {
                    int rec = optionsStore.addRecord(options,
						     0, options.length);
                } catch (RecordStoreException ex) {
                    alert.setString("Could not add options record");
                    display.setCurrent(alert);
                }
            } catch (RecordStoreException ex) {
                alert.setString("Could not save options");
                display.setCurrent(alert);
            }
        }
    
private voidsetupImageList()
Check the attributes in the descriptor that identify images and titles and initialize the lists of imageNames and imageList.

The attributes are named "PhotoTitle-n" and "PhotoImage-n". The value "n" must start at "1" and increment by 1.

        imageNames = new Vector();
        imageList = new List("Images", List.IMPLICIT);
        imageList.addCommand(exitCommand);
	imageList.addCommand(aboutCommand);
        imageList.setCommandListener(this);

	for (int n = 1; n < 100; n++) {
	    String nthImage = "PhotoImage-"+ n;
	    String image = getAppProperty(nthImage);
	    if (image == null || image.length() == 0)
		break;

            String nthTitle = "PhotoTitle-" + n;
            String title = getAppProperty(nthTitle);
            if (title == null || title.length() == 0)
		title = image;

            imageNames.addElement(image);
            imageList.append(title, null);
	}
        imageNames.addElement("testchart:");
        imageList.append("Test Chart", null);
    
protected voidstartApp()
Start up the Hello MIDlet by setting the PhotoFrame and loading the initial images.

        if (imageList.size() > 0) {
            display.setCurrent(imageList);
            openOptions();
            restoreOptions();
        } else {
            alert.setString("No images configured.");
            display.setCurrent(alert, imageList);
        }