Mega Code Archive

 
Categories / Java / J2ME
 

A simple Photo and Animation Album

/*  * @(#)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;   }     }