/mugcommander/java/source/com/mucommander/ui/icon/AnimatedIcon.java
Java | 374 lines | 124 code | 56 blank | 194 comment | 25 complexity | 719762c769ed8efaa7a3d78a9b69a844 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
- /*
- * This file is part of muCommander, http://www.mucommander.com
- * Copyright (C) 2002-2010 Maxence Bernard
- *
- * muCommander is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * muCommander is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.mucommander.ui.icon;
- import javax.swing.CellRendererPane;
- import javax.swing.Icon;
- import javax.swing.SwingUtilities;
- import javax.swing.Timer;
- import java.awt.Component;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.Point;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.awt.geom.AffineTransform;
- import java.lang.ref.WeakReference;
- import java.util.HashSet;
- /**
- * <code>javax.swing.Icon</code> implementation that manages animation.
- * <p>
- * This heavily borrows code from Technomage's <code>furbelow</code> package, distributed
- * under the GNU Lesser General Public License.<br>
- * The original source code can be found <a href="http://furbelow.svn.sourceforge.net/viewvc/furbelow/trunk/src/furbelow">here</a>.
- * </p>
- * @author twall, Nicolas Rinaudo
- */
- public abstract class AnimatedIcon implements Icon {
- // - Default values ------------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /** Default number of frames per animation. */
- public static final int DEFAULT_FRAME_COUNT = 8;
- /** Default number of milliseconds between each frame. */
- public static final int DEFAULT_FRAME_DELAY = 1000 / DEFAULT_FRAME_COUNT;
- // - Instance fields -----------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /** All tracked components. */
- private HashSet<TrackedComponent> components = new HashSet<TrackedComponent>();
- /** Timer used to take the animation from one frame to the next. */
- private Timer timer;
- /** Index of the current frame. */
- private int currentFrame;
- /** Total number of frames in the animation. */
- private int frameCount;
- /** Whether or not the animation should be running. */
- private boolean animate;
- // - Initialisation ------------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Creates a new animated icon.
- * <p>
- * This is a convenience constructor and is strictly equivalent to calling
- * <code>{@link #AnimatedIcon(int,int)}({@link #DEFAULT_FRAME_COUNT}, {@link #DEFAULT_FRAME_DELAY});</code>
- * </p>
- */
- public AnimatedIcon() {this(DEFAULT_FRAME_COUNT, DEFAULT_FRAME_DELAY);}
- /**
- * Creates a new animated icon with the specified number of frames.
- * <p>
- * This is a convenience constructor and is strictly equivalent to calling
- * <code>{@link #AnimatedIcon(int,int)}(frameCount, {@link #DEFAULT_FRAME_DELAY});</code>
- * </p>
- * @param frameCount number of frames in the animation.
- */
- public AnimatedIcon(int frameCount) {this(frameCount, DEFAULT_FRAME_DELAY);}
- /**
- * Creates a new animated icon with the specified number of frames and repaint delay.
- * @param frameCount number of frames in the animation.
- * @param repaintDelay number of milliseconds to sleep between each frame.
- */
- public AnimatedIcon(int frameCount, int repaintDelay) {
- // Initialises the animation timer.
- timer = new Timer(repaintDelay, new AnimationUpdater(this));
- timer.setRepeats(true);
- // Initialises frame control.
- setFrameCount(frameCount);
- setFrameDelay(repaintDelay);
- }
- // - Abstract methods ----------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Returns the icon's width.
- * @return the icon's width.
- */
- public abstract int getIconWidth();
- /**
- * Returns the icon's height.
- * @return the icon's height.
- */
- public abstract int getIconHeight();
- /**
- * Paints the current frame.
- * @param c component in which the frame is being painted.
- * @param g graphics in which to paint the frame.
- * @param x horizontal coordinate at which to paint the frame.
- * @param y vertical coordinate at which to paint the frame.
- */
- protected abstract void paintFrame(Component c, Graphics g, int x, int y);
- // - Frame management ----------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Sets the total number of frames in the animation.
- * @param count total number of frames in the animation.
- */
- public synchronized void setFrameCount(int count) {this.frameCount = count;}
- /**
- * Returns the total number of frames in the animation.
- * @return the total number of frames in the animation.
- */
- public synchronized int getFrameCount() {return frameCount;}
- /**
- * Returns the index of the current frame in the animation.
- * @return the index of the current frame in the animation.
- */
- public synchronized int getFrame() {return currentFrame;}
- /**
- * Sets the index of the current frame in the animation.
- * <p>
- * If the method does actually change the current frame, it will trigger a repaint.
- * </p>
- * @param frame index of the current frame in the animation.
- */
- public synchronized void setFrame(int frame) {
- if(frame != currentFrame) {
- if(frame == 0)
- currentFrame = 0;
- else
- currentFrame = frame % frameCount;
- repaint();
- }
- }
- /**
- * Takes the animation to its next frame.
- * <p>
- * This is a convenience method and is strictly equivalent to calling
- * <code>{@link #setFrame(int) setFrame}({@link #getFrame() getFrame()} + 1)</code>.
- * </p>
- */
- public synchronized void nextFrame() {setFrame(currentFrame + 1);}
- /**
- * Sets the number of milliseconds the animation will sleep between each frame.
- * <p>
- * If set to 0, the animation will stop.
- * </p>
- * @param delay number of milliseconds the animation will sleep between each frame.
- */
- public synchronized void setFrameDelay(int delay) {timer.setDelay(delay);}
- /**
- * Starts / stops the animation.
- * @param a whether the animation should be started or stopped.
- */
- public synchronized void setAnimated(boolean a) {
- // Starts the animation if necessary.
- if(a) {
- if(!timer.isRunning())
- timer.restart();
- }
- // Stops the animation if necessary.
- else if(timer.isRunning())
- timer.stop();
- animate = a;
- }
- /**
- * Returns <code>true</code> if the animation is currently running.
- * <p>
- * Note that this method will return <code>true</code> if the animation is <b>meant</b> to be running,
- * for example if the icon is not visible but would be animated if it was.
- * </p>
- * @return <code>true</code> if the animation is currently running, <code>false</code>.
- */
- public synchronized boolean isAnimated() {return animate;}
- /**
- * Returns the number of milliseconds the animation will sleep between each frame.
- * @return the number of milliseconds the animation will sleep between each frame.
- */
- public synchronized int getFrameDelay() {return timer.getDelay();}
- // - Painting ------------------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Paints the icon's current frame.
- * @param c component in which to paint the icon.
- * @param g graphic context in which to paint the icon.
- * @param x horizontal coordinate at which to paint the icon.
- * @param y vertical coordinate at which to paint the icon.
- */
- public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
- // Paints the current frame.
- paintFrame(c, g, x, y);
- // Stores the component and starts / restarts the timer if necessary.
- if(c != null) {
- AffineTransform transform;
- transform = ((Graphics2D)g).getTransform();
- components.add(new TrackedComponent(c, x, y, (int)(getIconWidth() * transform.getScaleX()), (int)(getIconHeight() * transform.getScaleY())));
- // Restarts the timer if necessary.
- if(!timer.isRunning() && animate)
- timer.restart();
- }
- }
- /**
- * Forces the icon to repaint.
- */
- protected synchronized void repaint() {
- // If the component list is empty, we can stop the timer.
- if(components.isEmpty())
- timer.stop();
- // Repaints all pending components.
- else {
- for(TrackedComponent comp : components)
- comp.repaint();
- components.clear();
- }
- }
- @Override
- protected void finalize() throws Throwable {
- // Forces the timer to stop when the animation isn't used anymore.
- timer.stop();
- super.finalize();
- }
- // - Container tracking --------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Used to keep track of the various components in which an animated icon is being painted.
- * @author twall, Nicolas Rinaudo
- */
- private static class TrackedComponent {
- /** Component in which the icon must be painted. */
- private Component component;
- /** Horizontal coordinate at which the icon should be painted. */
- private int x;
- /** Vertical coordinate at which the icon should be painted. */
- private int y;
- /** Width of the icon (used for clipping). */
- private int width;
- /** Height of the icon (used for clipping). */
- private int height;
- /** Component's hashcode. */
- private int hashCode;
- /**
- * Creates a new tracked component.
- * @param c component in which to paint the icon.
- * @param x horizontal coordinate at which to paint the icon.
- * @param y vertical coordinate at which to paint the icon.
- * @param width width of the icon.
- * @param height height of the icon.
- */
- public TrackedComponent(Component c, int x, int y, int width, int height) {
- Component ancestor;
- // Identifies the component that displays the icon.
- if((ancestor = findNonRendererAncestor(c)) != c) {
- Point pt = SwingUtilities.convertPoint(c, x, y, ancestor);
- c = ancestor;
- x = pt.x;
- y = pt.y;
- }
- // Stores all the necessary information and computes the tracked component's hashcode.
- component = c;
- this.x = x;
- this.y = y;
- this.width = width;
- this.height = height;
- hashCode = (x + "," + y + ":" + c.hashCode()).hashCode();
- }
- public int hashCode() {return hashCode;}
- /**
- * Finds the specified component's first non-renderer ancestor.
- * @param c component whose ancestors should be explored.
- */
- private Component findNonRendererAncestor(Component c) {
- Component ancestor;
- ancestor = SwingUtilities.getAncestorOfClass(CellRendererPane.class, c);
- if (ancestor != null && ancestor != c && ancestor.getParent() != null)
- c = findNonRendererAncestor(ancestor.getParent());
- return c;
- }
- /**
- * Forces the tracked component to repaint the animated icon.
- */
- public void repaint() {component.repaint(x, y, width, height);}
- }
- // - Timer management ----------------------------------------------------------------
- // -----------------------------------------------------------------------------------
- /**
- * Receives timer events and notifies the icon.
- * @author twall, Nicolas Rinaudo
- */
- private static class AnimationUpdater implements ActionListener {
- /** Weak reference to the animation. */
- private WeakReference<AnimatedIcon> icon;
- /**
- * Creates a new animation updater on the specified icon.
- * @param icon animation to update.
- */
- public AnimationUpdater(AnimatedIcon icon) {this.icon = new WeakReference<AnimatedIcon>(icon);}
- /**
- * Notifies the icon that it should update.
- * @param event ignored.
- */
- public void actionPerformed(ActionEvent event) {
- AnimatedIcon i;
- // Makes sure the animation hasn't been garbage collected.
- if((i = icon.get()) != null)
- i.nextFrame();
- }
- }
- }