/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.desktoputil; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.util.*; import java.util.List; import javax.swing.*; import javax.swing.Timer; /** * AnimatedImage will display a series of Images in a predetermined sequence. * This sequence can be configured to keep repeating or stop after a specified * number of cycles. * * An AnimatedImage cannot be shared by different components. However, * the Images added to the AnimatedImage can be shared. * * The animation sequence is a simple sequential display of each Image. When the * end is reached the animation restarts at the first Image. Images are * displayed in the order in which they were added. To create custom animation * sequences you will need to override the getNextIconIndex() and * isCycleCompleted() methods. * * @author Marin Dzhigarov */ public class AnimatedImage extends BufferedImage implements ActionListener { /** * The default delay between switching over the next image. */ private final static int DEFAULT_DELAY = 500; /** * The default number of cycles for the animation. * -1 means that the animation will go on forever. */ private final static int DEFAULT_CYCLES = -1; /** * The list of actual images that are going to be displayed */ private List images = new ArrayList(); /** * The JComponent that will display the Image */ private JComponent component; /** * The number of cycles for the animation. * -1 means that the animation will go on forever. */ private int cycles; private boolean showFirstImage = false; private int imageWidth; private int imageHeight; /** * The index of the currently displayed Image */ private int currentImageIndex; /** * The current number of completed cycles. The animation will go on while * cyclesCompleted < cycles. */ private int cyclesCompleted; private boolean animationFinished = true; /** * The Timer that handles switching between Images. */ private Timer timer; /** * The Graphics2D object used for painting. */ private final Graphics2D g2d; /** * Create an AnimatedImage that will continuously cycle with the * default (500ms). * * @param component the component the image will be painted on * @param images the Images to be painted as part of the animation */ public AnimatedImage(JComponent component, Image... images) { this(component, DEFAULT_DELAY, images); } /** * Create an AnimatedImage that will continuously cycle with the specified * delay * * @param component the component the image will be painted on * @param delay the delay between painting each image, in milli seconds * @param images the Images to be painted as part of the animation */ public AnimatedImage(JComponent component, int delay, Image... images) { this(component, delay, DEFAULT_CYCLES, images); } /** * Create an AnimatedImage specifying the required delay between painting * each image and the number of times to repeat the animation sequence * * @param component the component the image will be painted on * @param delay the delay between painting each image, in milli seconds * @param cycles the number of times to repeat the animation sequence * @param images the Images to be painted as part of the animation */ public AnimatedImage( JComponent component, int delay, int cycles, Image... images) { super( images[0].getWidth(null), images[0].getHeight(null), BufferedImage.TYPE_INT_ARGB); this.component = component; g2d = (Graphics2D) getGraphics(); setCycles( cycles ); for (int i = 0; i < images.length; i++) { if (images[i] == null) { throw new IllegalArgumentException("Images can not be null"); } else { addImage(images[i]); } } timer = new Timer(delay, this); } /** * Add Image to be used in the animation. * * @param image the image to be added */ public void addImage(Image image) { if (image != null) { this.images.add(image); calculateImageDimensions(); } } /** * Calculate the width and height of the Image based on the maximum * width and height of any individual Image. */ private void calculateImageDimensions() { imageWidth = 0; imageHeight = 0; for (Image image : images) { imageWidth = Math.max(imageWidth, image.getWidth(null)); imageHeight = Math.max(imageHeight, image.getHeight(null)); } } /** * Get the index of the currently visible Image * * @return the index of the Icon */ public int getCurrentImageIndex() { return currentImageIndex; } /** * Set the index of the Image to be displayed and then repaint the Image. * * @param index the index of the Image to be displayed */ public void setCurrentImageIndex(int index) { currentImageIndex = index; AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC); g2d.setComposite(ac); g2d.drawImage(getImage(currentImageIndex), 0, 0, component); component.repaint(); } /** * Get the remaining cycles to complete before the animation stops. * * @return the number of remaining cycles */ public int getRemainingCycles() { return cycles; } /** * Specify the number of times to repeat each animation sequence, or cycle. * * @param cycles the number of cycles to complete before the animation * stops. The default is -1, which means the animation is * continuous. */ public void setCycles(int cycles) { this.cycles = cycles; } /** * Returns the delay between painting each Image * * @return the delay */ public int getDelay() { return timer.getDelay(); } /** * Specify the delay between painting each Image * * @param delay the delay between painting each Image (in milli seconds) */ public void setDelay(int delay) { timer.setDelay(delay); } /** * Returns the Image at the specified index. * * @param index the index of the Image to be returned * @return the Image at the specifed index * @exception IndexOutOfBoundsException if the index is out of range */ public Image getImage(int index) { return images.get( index ); } /** * Returns the number of Images that are contained in the animation * * @return the total number of Images */ public int getImagesCount() { return images.size(); } /** * Get the showFirstImage * * @return the showFirstImage value */ public boolean isShowFirstImage() { return showFirstImage; } /** * Displays the first image of the animation after the last cycle has passed * If set to false, the last image will remain set after the last * animation cycle. * * @param showFirstImage true when the first image is to be displayed, * false otherwise */ public void setShowFirstImage(boolean showFirstImage) { this.showFirstImage = showFirstImage; } /** * Pauses the animation. The animation can be restarted from the * current Image using the restart() method. */ public void pause() { timer.stop(); } /** * Start the animation from the beginning. */ public void start() { if (!timer.isRunning()) { setCurrentImageIndex(0); animationFinished = false; cyclesCompleted = 0; timer.start(); } } /** * Restarts the animation from where the animation was paused. Or, if the * animation has finished, it will be restarted from the beginning. */ public void restart() { if (!timer.isRunning()) { if (animationFinished) start(); else timer.restart(); } } /** * Stops the animation. The first icon will be redisplayed. */ public void stop() { timer.stop(); setCurrentImageIndex(0); animationFinished = true; } /** * Gets the width of this image. * * @return the width of the image in pixels. */ public int getWidth(ImageObserver obs) { return imageWidth; } /** * Gets the height of this image. * * @return the height of the image in pixels. */ public int getHeight(ImageObserver obs) { return imageHeight; } /** * Controls the image animation that is scheduled by the Timer */ public void actionPerformed(ActionEvent e) { // Display the next Image in the animation sequence setCurrentImageIndex( getNextImageIndex(currentImageIndex, images.size())); // Track the number of cycles that have been completed if (isCycleCompleted(currentImageIndex, images.size())) { cyclesCompleted++; } // Stop the animation when the specified number of cycles is completed if (cycles > 0 && cycles <= cyclesCompleted) { timer.stop(); animationFinished = true; // Display the first Image when required if (isShowFirstImage() && getCurrentImageIndex() != 0) { new Thread(new Runnable() { @Override public void run() { // Wait one more delay interval before displaying the // first Image try { Thread.sleep( timer.getDelay() ); setCurrentImageIndex(0); } catch(InterruptedException e) {} } }).start(); } } } /** * Gets the index of the next Image to be displayed. Normally, images will * be displayed in the order they were added to the AnimatedImage. * If however, a custom animation sequence is required one can extend this * method and achieve a greater control over the animation sequence. * * @param currentIndex the index of the Image currently displayed * @param imageCount the number of Images to be displayed * @return the index of the next Image to be displayed */ protected int getNextImageIndex(int currentIndex, int imageCount) { return ++currentIndex % imageCount; } /** * Checks if the currently displayed Image is the last image of the * animation sequence. This marks the completion of a single animation * cycle. * If a custom animation sequence is required one can extend this * method to achieve a greater control over the animation sequence. * * @param currentIndex the index of the Image currently displayed * @param imageCount the number of Images to be displayed * @return the index of the next Image to be displayed */ protected boolean isCycleCompleted(int currentIndex, int imageCount) { return currentIndex == imageCount - 1; } }