A simple Photo and Animation Album : Application « J2ME « Java






A simple Photo and Animation Album

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;
  }
   
}



           
       








Related examples in the same category

1.Populate Address BookPopulate Address Book
2.Address Book MIDletAddress Book MIDlet
3.Personal Information Manager
4.Website - J2ME Enterprise Development
5.Todo MIDletTodo MIDlet