/*
* @(#)PhotoAlbum.java 1.6 01/04/04
*
* Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
import java.io.IOException;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.Vector;
/**
* The PhotoAlbum MIDlet class provides the commands and screens
* that implement a simple Photo and Animation Album.
* The images and animations to be displayed are configured
* in the descriptor file with attributes.
* <p>
* It provides simple options to vary the speed of display
* and the picture frames used.
*
*/
public class PhotoAlbum extends MIDlet
implements CommandListener, ItemStateListener
{
private Display display; // The display for this MIDlet
private PhotoFrame frame; // The Frame and Canvas for images
private ChoiceGroup borderChoice; // List of border choices
private ChoiceGroup speedChoice; // List of speed choices
private Form optionsForm; // The form holding the options
private Alert alert; // The Alert used for errors
private Vector imageNames; // Strings with the image names
private List imageList; // List of Image titles
private Command exitCommand; // The exit command
private Command okCommand; // The ok command
private Command optionsCommand; // The command to edit options
private Command backCommand; // The command to go back
/**
* Construct a new PhotoAlbum MIDlet and initialize the base
* options and main PhotoFrame to be used when the MIDlet is
* started.
*/
public PhotoAlbum() {
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.SCREEN, 3);
frame = new PhotoFrame();
frame.setStyle(2);
frame.setSpeed(2);
frame.addCommand(optionsCommand);
frame.addCommand(backCommand);
frame.setCommandListener(this);
alert = new Alert("Warning");
setupImages();
}
/**
* Start up the MIDlet by setting the display
* to show the image name list.
*/
protected void startApp() {
display.setCurrent(imageList);
}
/**
* Pausing is easy since there are no background activities
* or record stores that need to be closed.
*/
protected void pauseApp() {
frame.reset(); // Discard images cached in the frame
}
/**
* Destroy must cleanup everything not handled by the garbage
* collector.
*/
protected void destroyApp(boolean unconditional) {
frame.reset(); // Discard images cached in the frame
}
/**
* Respond to commands, including exit.
* On the exit command, cleanup and notify that
* the MIDlet has been destroyed.
*/
public void commandAction(Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
} else if (c == optionsCommand) {
display.setCurrent(genOptions());
} else if (c == okCommand && s == optionsForm) {
display.setCurrent(frame);
} else if (c == List.SELECT_COMMAND) {
int i = imageList.getSelectedIndex();
String image = (String)imageNames.elementAt(i);
try {
frame.setImage(image);
display.setCurrent(frame);
} catch (java.io.IOException e) {
alert.setString("Unable to locate " + image);
display.setCurrent(alert, imageList);
}
} else if (c == backCommand) {
display.setCurrent(imageList);
}
}
/**
* Listener for changes to options.
*/
public void itemStateChanged(Item item) {
if (item == borderChoice) {
frame.setStyle(borderChoice.getSelectedIndex());
} else if (item == speedChoice) {
frame.setSpeed(speedChoice.getSelectedIndex());
}
}
/**
* 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.
*/
private Screen genOptions() {
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.setSelectedIndex(2, true);
optionsForm.append(speedChoice);
borderChoice = new ChoiceGroup("Borders",
Choice.EXCLUSIVE);
borderChoice.append("None", null);
borderChoice.append("Plain", null);
borderChoice.append("Fancy", null);
borderChoice.setSelectedIndex(2, true);
optionsForm.append(borderChoice);
}
return optionsForm;
}
/**
* Check the attributes in the Descriptor that identify
* images and titles and initialize the lists imageNames
* and imageList.
*/
private void setupImages() {
imageNames = new Vector();
imageList = new List("Images", List.IMPLICIT);
imageList.addCommand(exitCommand);
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);
}
}
}
/*
* @(#)Animation.java 1.5 01/04/04
*
* Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* An Animation contains the set of images to display.
* Images are read from resource files supplied in the
* JAR file.
* <p>
* This implementation keeps the Images in the heap.
* If memory is short, a more deliberate management
* of Image may be required.
*/
class Animation {
/**
* Location to draw the animation, set these fields to
* change the location where the image is drawn.
*/
int x, y;
/**
* The width and the height of the images (max of all if they
* are different).
* They are set when images are loaded and should not be changed.
*/
int width, height;
/**
* Vector of images in the sequence.
*/
private Vector images;
/**
* Current index into the sequence of images.
*/
private int index;
/**
* Size of sequence of images.
* Set to a large number until the last image of
* the sequence has been read.
*/
private int size;
/**
* Prefix or name of the image.
*/
private String prefix;
/**
* Create a new Animation.
*/
Animation() {
images = new Vector(30);
}
/**
* Advance to the next image.
* If the number of images is known then just advance
* and wrap around if necessary.
* If the number of images is not known then when
* advancing off the end of the known images try to
* create a new image using the pattern.
* When an attempt fails that sets the number of images.
*/
void next() {
int nextindex = index + 1;
if (nextindex >= size) {
index = 0;
} else if (nextindex >= images.size()) {
// Try to read the next image
// If that works put it into the images vector
try {
String name = prefix + nextindex + ".png";
Image image = Image.createImage(name);
images.setSize(nextindex+1);
images.setElementAt(image, nextindex);
index = nextindex;
} catch (IOException ex) {
// No more images, set the size of the sequence.
size = nextindex;
index = 0;
} catch (Exception e) {
size = nextindex;
index = 0;
}
} else {
// Index is within range of Images already read
index = nextindex;
}
}
/**
* Back up to the previous image.
* Wrap around to the end if at the beginning.
*/
void previous() {
index--;
if (index < 0) {
index = images.size()-1;
}
}
/**
* Paint the current image in the sequence.
* The image is drawn to the target graphics context
* at the x, and y of the Animation.
* @param g graphics context to which the next image is drawn.
*/
public void paint(Graphics g) {
if (images.size() > 0) {
g.drawImage((Image)images.elementAt(index), x, y, 0);
}
}
/**
* Load Images from resource files using
* <code>Image.createImage</code>.
* The first image is loaded to determine whether it is a
* single image or a sequence of images and to make sure it exists.
* Subsequent images are loaded on demand when they are needed.
* 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".
* <p>
* Subsequent images are loaded when they are needed. See
* <code>next</code> and <code>previous</code> for details.
* @param name the name or prefix of the resource image names
* @exception IOException is thrown if the image or the first
* of the sequence cannot be found.
* @exception OutOfMemoryError if no memory can be allocated for
* the image.
*/
void loadImage(String prefix) throws IOException {
this.prefix = prefix;
Image image = null;
images.setSize(0);
index = 0;
try {
// Try the name supplied for the single image case.
// If it is found then do the setup and return
image = Image.createImage(prefix);
size = 1;
} catch (IOException ex) {
// Use the prefix + "0.png" to locate the first of
// a series of images.
String name = prefix + "0.png";
image = Image.createImage(name);
size = 999999999;
}
width = image.getWidth();
height = image.getHeight();
images.addElement(image);
}
/**
* Reset the Animation to reduce memory usage.
* Discard all but the first image.
*/
void reset() {
if (images.size() > 0) {
for (int i = 0; i < images.size(); i++)
images.setElementAt(null, i);
}
}
}
/*
* @(#)PhotoFrame.java 1.6 01/04/04
* Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* This class provides the picture frame and drives the animation
* of the frames and the picture. It handles the starting and stopping
* of the Animation and contains the Thread used to do
* the timing and requests that the Canvas is repainted
* periodically. Additionally, it has controls for border
* style and animation speed.
*/
class PhotoFrame extends Canvas implements Runnable {
private Animation animation; // The Animation sequencer.
private int speed; // Animation speed set
private Thread thread; // Thread used for triggering repaints
private long paintTime; // Time of most recent paint
private Image image; // Buffer image of screen
private Image bimage; // Pattern used for border
private int style; // Border style
/*
* Mapping of speed values to delays in milliseconds.
* Indices map to those in the ChoiceGroup.
*/
private final static int speeds[] = {999999999, 500, 250, 100};
/**
* Create a new PhotoFrame. Creates an offscreen mutable
* image into which the border is drawn.
* Border style is none (0).
* Speed is stopped (0) until set.
*/
PhotoFrame() {
animation = new Animation();
image = Image.createImage(getWidth(), getHeight());
setStyle(0);
setSpeed(0);
}
/**
* Load a new photo into the frame.
* Load the images into the Animation and pick
* where the image should be placed on the canvas.
* Also draw the frame into the buffered image based
* on the animation size.
* If the images can't be loaded, just reset the origin
* and throw an IOException.
* @param name the prefix of the resource to load.
* @throws IOException when no images can be loaded.
*/
void setImage(String prefix) throws IOException {
try {
animation.loadImage(prefix);
animation.x = (getWidth() - animation.width) / 2;
animation.y = (getHeight() - animation.height) / 2;
paintFrame(style, animation.x, animation.y,
animation.width, animation.height);
} catch (java.io.IOException ex) {
// No image to display just show an empty frame.
animation.x = 0;
animation.y = 0;
paintFrame(style, 10, 10,
getWidth()-20, getHeight()-20);
throw ex;
}
}
/**
* Reset the PhotoFrame so it holds minimal resources
* by resetting the animation.
* The animation thread is stopped.
*/
void reset() {
animation.reset();
image = null;
thread = null;
}
/**
* Handle key events. FIRE events toggle between
* running and stopped. LEFT and RIGHT key events
* when stopped show the previous or next image.
*/
protected void keyPressed(int keyCode) {
int action = getGameAction(keyCode);
switch (action) {
case RIGHT:
if (thread == null) {
animation.next();
repaint();
}
break;
case LEFT:
if (thread == null) {
animation.previous();
repaint();
}
break;
case FIRE:
// Use FIRE to toggle the activity of the thread
if (thread == null) {
thread = new Thread(this);
thread.start();
} else {
synchronized (this) {
// Wake up the thread
this.notify();
thread = null;
}
}
break;
}
}
/**
* Handle key repeat events as regular key events.
*/
protected void keyRepeated(int keyCode) {
keyPressed(keyCode);
}
/**
* Set the animation speed.
* @param speed speedo of animation 0-3;
* 0 == stop; 1 = slow, 2 = medium, 3 = fast.
*/
void setSpeed(int speed) {
this.speed = speed;
}
/**
* Set the frame style.
* Recreate the photo frame image from the current animation
* and the new style.
*/
void setStyle(int style) {
this.style = style;
paintFrame(style, animation.x, animation.y,
animation.width, animation.height);
}
/**
* Notified when Canvas is made visible.
* Create the thread to run the animation timing.
*/
protected void showNotify() {
thread = new Thread(this);
thread.start();
}
/**
* Notified when the Canvas is no longer visible.
* Signal the running Thread that it should stop.
*/
protected void hideNotify() {
thread = null;
}
/**
* Paint is called whenever the canvas should be redrawn.
* It clears the canvas and draws the frame and the
* current frame from the animation.
* @param g the Graphics context to which to draw
*/
protected void paint(Graphics g) {
paintTime = System.currentTimeMillis();
if (image != null) {
// Draw the frame unless only the picture is being
// re-drawn.
// This is the inverse of the usual clip check.
int cx = 0, cy = 0, cw = 0, ch = 0;
if ((cx = g.getClipX()) < animation.x ||
(cy = g.getClipY()) < animation.y ||
((cx + (cw = g.getClipWidth())) >
(animation.x + animation.width)) ||
((cy + (ch = g.getClipHeight())) >
(animation.y + animation.height))) {
g.drawImage(image, 0, 0,
Graphics.LEFT|Graphics.TOP);
}
// Draw the image if it intersects the clipping region
if (intersectsClip(g, animation.x, animation.y,
animation.width, animation.height)) {
animation.paint(g);
}
}
}
/**
* Return true if the specified rectangle does intersect the
* clipping rectangle of the graphics object. If it returns true
* then the object must be drawn otherwise it would be clipped
* completely.
* The checks are done in a order with early exits to make this
* as inexpensive as possible.
* @param g the Graphics context to check
* @param x the upper left corner of the rectangle
* @param y the upper left corner of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @return true if the rectangle intersects the clipping region
*/
boolean intersectsClip(Graphics g, int x, int y, int w, int h) {
int cx = g.getClipX();
if (x + w <= cx)
return false;
int cw = g.getClipWidth();
if (x > cx + cw)
return false;
int cy = g.getClipY();
if (y + h <= cy)
return false;
int ch = g.getClipHeight();
if (y > cy + ch)
return false;
return true;
}
/**
* Paint the photo frame into the buffered screen image.
* This will avoid drawing each of its parts on each repaint.
* Paint will only need to put the image into the frame.
* @param style the style of frame to draw.
* @param x the x offset of the image.
* @param y the y offset of the image
* @param width the width of the anmiation image
* @param height the height of the animation image
*/
private void paintFrame(int style, int x, int y,
int width, int height) {
Graphics g = image.getGraphics();
// Clear the entire canvas to white
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth()+1, getHeight()+1);
// Set the origin of the image and paint the border and image.
g.translate(x, y);
paintBorder(g, style, width, height);
}
/**
* Runs the animation and makes the repaint requests.
* The thread will exit when it is no longer the current
* Animation thread.
*/
public void run() {
Thread me = Thread.currentThread();
long scheduled = System.currentTimeMillis();
paintTime = scheduled;
while (me == thread) {
synchronized (this) {
try {
// Update when the next frame should
// be drawn and compute the delta
scheduled += speeds[speed];
long delta = scheduled - paintTime;
if (delta > 0) {
this.wait(delta);
}
animation.next();
// Request a repaint only of the image
repaint(animation.x, animation.y,
animation.width,
animation.height);
} catch (InterruptedException e) {
}
}
}
}
/**
* Draw a border of the selected style.
* Style:
* <OL>
* <LI> Style 0: No border is drawn.
* <LI> Style 1: A simple border is drawn
* <LI> Style 2: The border is outlined and an image
* is created to tile within the border.
* </OL>
* @param g graphics context to which to draw.
* @param x the horizontal offset in the frame of the image.
* @param y the vertical offset in the frame
* @param w the width reserved for the image
* @param h the height reserved of the image
*/
private void paintBorder(Graphics g, int style, int w, int h) {
if (style == 1) {
g.setColor(0x808080);
g.drawRect(-1, -1, w + 1, h + 1);
g.drawRect(-2, -2, w + 3, h + 3);
}
if (style == 2) {
// Draw fancy border with image between outer
// and inner rectangles
if (bimage == null)
bimage = genBorder();
int bw = bimage.getWidth();
int bh = bimage.getHeight();
int i;
// Draw the inner and outer solid border
g.setColor(0x808080);
g.drawRect(-1, -1, w + 1, h + 1);
g.drawRect(-bw - 2, -bh - 2,
w + bw * 2 + 3, h + bh * 2 + 3);
// Draw it in each corner
g.drawImage(bimage, -1, -1,
Graphics.BOTTOM|Graphics.RIGHT);
g.drawImage(bimage, -1, h + 1,
Graphics.TOP|Graphics.RIGHT);
g.drawImage(bimage, w + 1, -1,
Graphics.BOTTOM|Graphics.LEFT);
g.drawImage(bimage, w + 1, h + 1,
Graphics.TOP|Graphics.LEFT);
// Draw the embedded image down left and right sides
for (i = ((h % bh) / 2); i < h - bh; i += bh) {
g.drawImage(bimage, -1, i,
Graphics.RIGHT|Graphics.TOP);
g.drawImage(bimage, w + 1, i,
Graphics.LEFT|Graphics.TOP);
}
// Draw the embedded image across the top and bottom
for (i = ((w % bw) / 2); i < w - bw; i += bw) {
g.drawImage(bimage, i, -1,
Graphics.LEFT|Graphics.BOTTOM);
g.drawImage(bimage, i, h + 1,
Graphics.LEFT|Graphics.TOP);
}
}
}
/**
* Create an image for the border.
* The border consists of a simple "+" drawn in a 5x5 image.
* Fill the image with white and draw the "+" as magenta.
*/
private Image genBorder() {
Image image = Image.createImage(5, 5);
Graphics g = image.getGraphics();
g.setColor(255, 255, 255);
g.fillRect(0, 0, 5, 5);
g.setColor(128, 0, 255);
g.drawLine(2, 1, 2, 3); // vertical
g.drawLine(1, 2, 3, 2); // horizontal
return image;
}
}
|