/*
* @(#)PhotoFrame.java 1.18 02/10/03 @(#)
*
* Copyright (c) 2000-2002 Sun Microsystems, Inc. All rights reserved.
* PROPRIETARY/CONFIDENTIAL
* Use is subject to license terms.
*/
package example.photoalbum;
import java.util.Vector;
import javax.microedition.lcdui.*;
/**
* This PhotoFrame 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 be repainted
* periodically.
* It controls the border style and animation speed.
*/
class PhotoFrame extends Canvas implements Runnable {
/** border style */
private int style;
/** animation speed set */
private int speed;
/** Vector of images to display */
private Vector images;
/** Index of next image to display */
private int index;
/** X offset of image in frame */
private int imageX;
/** X offset of image in frame */
private int imageY;
/** Width and height of image */
private int imageWidth, imageHeight;
/** Thread used for triggering repaints */
private Thread thread;
/** buffer image of the screen */
private Image image;
/** Pattern image used for border */
private Image bimage;
/** Time of most recent paint */
private long paintTime;
/** Time of most recent frame rate report */
private long statsTime;
/** Number of frames since last frame rate report */
int frameCount;
/** Last reported frame rate (for re-paint) */
int frameRate;
/**
* Create a new PhotoFrame.
* Create an offscreen mutable image into which the border is drawn.
* Border style is none (0).
* Speed is stopped (0) until set.
*/
PhotoFrame() {
image = Image.createImage(getWidth(), getHeight());
setStyle(0);
setSpeed(0);
}
/**
* Set the array of images to be displayed.
* Update the width and height of the image and draw
* the border to fit around it in the offscreen image.
* @param images a vector of images to be displayed.
*/
void setImages(Vector images) {
this.images = images;
if (images.size() > 0) {
Image image = (Image)images.elementAt(0);
imageWidth = image.getWidth();
imageHeight = image.getHeight();
} else {
imageWidth = 0;
imageHeight = 0;
}
index = 0;
imageX = (getWidth() - imageWidth) / 2;
imageY = (getHeight() - imageHeight) / 2;
genFrame(style, imageX, imageY, imageWidth, imageHeight);
}
/**
* Advance to the next image and wrap around if necessary.
*/
void next() {
if (images == null || ++index >= images.size()) {
index = 0;
}
}
/**
* Back up to the previous image.
* Wrap around to the end if at the beginning.
*/
void previous() {
if (images != null && --index < 0) {
index = images.size()-1;
} else {
index = 0;
}
}
/**
* Reset the PhotoFrame so it holds minimal resources.
* The animation thread is stopped.
*/
void reset() {
images = 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.
* @param keyCode of the key pressed
*/
protected void keyPressed(int keyCode) {
int action = getGameAction(keyCode);
switch (action) {
case RIGHT:
if (thread == null) {
next();
repaint();
}
break;
case LEFT:
if (thread == null) {
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 to change the timing
this.notify();
thread = null;
}
}
break;
}
}
/**
* Handle key repeat events as regular key events.
* @param keyCode of the key repeated
*/
protected void keyRepeated(int keyCode) {
keyPressed(keyCode);
}
/**
* Set the animation speed.
* Speed:
* <OL>
* <LI>0 = stop
* <LI>1 = slow
* <LI>2 = medium
* <LI>3 = fast
* <LI>4 = unlimited
* </OL>
* @param speed speedo of animation 0-3;
*/
void setSpeed(int speed) {
this.speed = speed;
statsTime = 0;
}
/**
* Get the speed at which animation occurs.
* @return the current speed.
* @see setSpeed
*/
int getSpeed() {
return speed;
}
/**
* Set the frame style.
* Recreate the photo frame image from the current style
* and location and size
* <p>
* 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 style the style of the border; 0 = none, 1 = simple,
* 2 = fancy.
*/
void setStyle(int style) {
this.style = style;
genFrame(style, imageX, imageY, imageWidth, imageHeight);
}
/**
* Get the style being used for borders.
* @return the style.
*/
int getStyle() {
return style;
}
/**
* Notified when Canvas is made visible.
* If there is more than one image to display
* create the thread to run the animation timing.
*/
protected void showNotify() {
if (images != null && images.size() > 1) {
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;
}
/**
* Return true if the specified rectangle does not 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
* @param g the Graphics context to check.
* @param x the x value of the upper left corner of the rectange
* @param y the y value of the upper left corner of the rectange
* @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;
}
/**
* 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 mythread = Thread.currentThread();
long scheduled = System.currentTimeMillis();
statsTime = scheduled;
paintTime = scheduled;
frameCount = 0;
frameRate = 0;
while (thread == mythread) {
synchronized (this) {
try {
// Update when the next frame should be drawn
// and compute the delta till then
scheduled += speeds[speed];
long delta = scheduled - paintTime;
if (delta > 0) {
this.wait(delta);
}
// Advance and repaint the screen
next();
repaint();
serviceRepaints();
} catch (InterruptedException e) {
}
}
}
}
/**
* Paint is called whenever the canvas should be redrawn.
* It clears the canvas and draws the frame and the current
* 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()) < imageX ||
(cy = g.getClipY()) < imageY ||
(cx + (cw = g.getClipWidth())) > (imageX + imageWidth) ||
(cy + (ch = g.getClipHeight())) > (imageY + imageHeight)) {
g.drawImage(image, 0, 0, Graphics.LEFT|Graphics.TOP);
if (frameRate > 0) {
g.fillRect(0, getHeight(), 60, 20);
g.drawString("FPS = " + frameRate, 0, getHeight(),
Graphics.BOTTOM|Graphics.LEFT);
}
}
// Draw the image if it intersects the clipping region
if (images != null &&
index < images.size() &&
intersectsClip(g, imageX, imageY, imageWidth, imageHeight)) {
g.drawImage((Image)images.elementAt(index),
imageX, imageY, Graphics.LEFT|Graphics.TOP);
}
frameCount++;
// Update Frame rate
int delta = (int)(paintTime - statsTime);
if (delta > 1000 && delta < 10000) {
frameRate = ((frameCount * 1000 + 500) / delta);
frameCount = 0;
statsTime = paintTime;
repaint(); // queue full repaint to display frame rate
}
}
}
/**
* 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 genFrame(int style, int x, int y, int width, int height) {
Graphics g = image.getGraphics();
// Clear the entire image to white
g.setColor(0xffffff);
g.fillRect(0, 0, image.getWidth()+1, image.getHeight()+1);
// Set the origin of the image and paint the border and image.
g.translate(x, y);
paintBorder(g, style, width, height);
}
/**
* Draw a border of the selected style.
* @param g graphics context to which to draw.
* @param style of the border to display
* @param w the width reserved for the image
* @param h the height reserved of the image
* @see setStyle
*/
private void paintBorder(Graphics g, int style, int w, int h) {
if (style == 1) {
g.setGrayScale(128);
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(); // Generate the border image
int bw = bimage.getWidth();
int bh = bimage.getHeight();
int i;
// Draw the inner and outer solid border
g.setGrayScale(128);
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.
* @return the image initialized with the pattern
*/
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;
}
/**
* Mapping of speed values to delays in ms.
* Indices map to those in the speed ChoiceGroup in the options form.
* The indices are: 0 = stop, 1 = slow, 2 = medium, 3 = fast.
* @see setSpeed
*/
private final static int speeds[] = {999999999, 500, 250, 100, 0};
}
|