www.pudn.com > j2mewireless_examples.zip > PhotoFrame.java


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

package examples.photoalbum;

import javax.microedition.lcdui.*;
import java.io.IOException;

/**
 * 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:
	 * 
    *
  1. Style 0: No border is drawn. *
  2. Style 1: A simple border is drawn *
  3. Style 2: The border is outlined and an image * is created to tile within the border. *
* @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; } }