PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/mugcommander/java/source/com/mucommander/ui/icon/AnimatedIcon.java

http://pagavcs.googlecode.com/
Java | 374 lines | 124 code | 56 blank | 194 comment | 25 complexity | 719762c769ed8efaa7a3d78a9b69a844 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. /*
  2. * This file is part of muCommander, http://www.mucommander.com
  3. * Copyright (C) 2002-2010 Maxence Bernard
  4. *
  5. * muCommander is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * muCommander is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. package com.mucommander.ui.icon;
  19. import javax.swing.CellRendererPane;
  20. import javax.swing.Icon;
  21. import javax.swing.SwingUtilities;
  22. import javax.swing.Timer;
  23. import java.awt.Component;
  24. import java.awt.Graphics;
  25. import java.awt.Graphics2D;
  26. import java.awt.Point;
  27. import java.awt.event.ActionEvent;
  28. import java.awt.event.ActionListener;
  29. import java.awt.geom.AffineTransform;
  30. import java.lang.ref.WeakReference;
  31. import java.util.HashSet;
  32. /**
  33. * <code>javax.swing.Icon</code> implementation that manages animation.
  34. * <p>
  35. * This heavily borrows code from Technomage's <code>furbelow</code> package, distributed
  36. * under the GNU Lesser General Public License.<br>
  37. * The original source code can be found <a href="http://furbelow.svn.sourceforge.net/viewvc/furbelow/trunk/src/furbelow">here</a>.
  38. * </p>
  39. * @author twall, Nicolas Rinaudo
  40. */
  41. public abstract class AnimatedIcon implements Icon {
  42. // - Default values ------------------------------------------------------------------
  43. // -----------------------------------------------------------------------------------
  44. /** Default number of frames per animation. */
  45. public static final int DEFAULT_FRAME_COUNT = 8;
  46. /** Default number of milliseconds between each frame. */
  47. public static final int DEFAULT_FRAME_DELAY = 1000 / DEFAULT_FRAME_COUNT;
  48. // - Instance fields -----------------------------------------------------------------
  49. // -----------------------------------------------------------------------------------
  50. /** All tracked components. */
  51. private HashSet<TrackedComponent> components = new HashSet<TrackedComponent>();
  52. /** Timer used to take the animation from one frame to the next. */
  53. private Timer timer;
  54. /** Index of the current frame. */
  55. private int currentFrame;
  56. /** Total number of frames in the animation. */
  57. private int frameCount;
  58. /** Whether or not the animation should be running. */
  59. private boolean animate;
  60. // - Initialisation ------------------------------------------------------------------
  61. // -----------------------------------------------------------------------------------
  62. /**
  63. * Creates a new animated icon.
  64. * <p>
  65. * This is a convenience constructor and is strictly equivalent to calling
  66. * <code>{@link #AnimatedIcon(int,int)}({@link #DEFAULT_FRAME_COUNT}, {@link #DEFAULT_FRAME_DELAY});</code>
  67. * </p>
  68. */
  69. public AnimatedIcon() {this(DEFAULT_FRAME_COUNT, DEFAULT_FRAME_DELAY);}
  70. /**
  71. * Creates a new animated icon with the specified number of frames.
  72. * <p>
  73. * This is a convenience constructor and is strictly equivalent to calling
  74. * <code>{@link #AnimatedIcon(int,int)}(frameCount, {@link #DEFAULT_FRAME_DELAY});</code>
  75. * </p>
  76. * @param frameCount number of frames in the animation.
  77. */
  78. public AnimatedIcon(int frameCount) {this(frameCount, DEFAULT_FRAME_DELAY);}
  79. /**
  80. * Creates a new animated icon with the specified number of frames and repaint delay.
  81. * @param frameCount number of frames in the animation.
  82. * @param repaintDelay number of milliseconds to sleep between each frame.
  83. */
  84. public AnimatedIcon(int frameCount, int repaintDelay) {
  85. // Initialises the animation timer.
  86. timer = new Timer(repaintDelay, new AnimationUpdater(this));
  87. timer.setRepeats(true);
  88. // Initialises frame control.
  89. setFrameCount(frameCount);
  90. setFrameDelay(repaintDelay);
  91. }
  92. // - Abstract methods ----------------------------------------------------------------
  93. // -----------------------------------------------------------------------------------
  94. /**
  95. * Returns the icon's width.
  96. * @return the icon's width.
  97. */
  98. public abstract int getIconWidth();
  99. /**
  100. * Returns the icon's height.
  101. * @return the icon's height.
  102. */
  103. public abstract int getIconHeight();
  104. /**
  105. * Paints the current frame.
  106. * @param c component in which the frame is being painted.
  107. * @param g graphics in which to paint the frame.
  108. * @param x horizontal coordinate at which to paint the frame.
  109. * @param y vertical coordinate at which to paint the frame.
  110. */
  111. protected abstract void paintFrame(Component c, Graphics g, int x, int y);
  112. // - Frame management ----------------------------------------------------------------
  113. // -----------------------------------------------------------------------------------
  114. /**
  115. * Sets the total number of frames in the animation.
  116. * @param count total number of frames in the animation.
  117. */
  118. public synchronized void setFrameCount(int count) {this.frameCount = count;}
  119. /**
  120. * Returns the total number of frames in the animation.
  121. * @return the total number of frames in the animation.
  122. */
  123. public synchronized int getFrameCount() {return frameCount;}
  124. /**
  125. * Returns the index of the current frame in the animation.
  126. * @return the index of the current frame in the animation.
  127. */
  128. public synchronized int getFrame() {return currentFrame;}
  129. /**
  130. * Sets the index of the current frame in the animation.
  131. * <p>
  132. * If the method does actually change the current frame, it will trigger a repaint.
  133. * </p>
  134. * @param frame index of the current frame in the animation.
  135. */
  136. public synchronized void setFrame(int frame) {
  137. if(frame != currentFrame) {
  138. if(frame == 0)
  139. currentFrame = 0;
  140. else
  141. currentFrame = frame % frameCount;
  142. repaint();
  143. }
  144. }
  145. /**
  146. * Takes the animation to its next frame.
  147. * <p>
  148. * This is a convenience method and is strictly equivalent to calling
  149. * <code>{@link #setFrame(int) setFrame}({@link #getFrame() getFrame()} + 1)</code>.
  150. * </p>
  151. */
  152. public synchronized void nextFrame() {setFrame(currentFrame + 1);}
  153. /**
  154. * Sets the number of milliseconds the animation will sleep between each frame.
  155. * <p>
  156. * If set to 0, the animation will stop.
  157. * </p>
  158. * @param delay number of milliseconds the animation will sleep between each frame.
  159. */
  160. public synchronized void setFrameDelay(int delay) {timer.setDelay(delay);}
  161. /**
  162. * Starts / stops the animation.
  163. * @param a whether the animation should be started or stopped.
  164. */
  165. public synchronized void setAnimated(boolean a) {
  166. // Starts the animation if necessary.
  167. if(a) {
  168. if(!timer.isRunning())
  169. timer.restart();
  170. }
  171. // Stops the animation if necessary.
  172. else if(timer.isRunning())
  173. timer.stop();
  174. animate = a;
  175. }
  176. /**
  177. * Returns <code>true</code> if the animation is currently running.
  178. * <p>
  179. * Note that this method will return <code>true</code> if the animation is <b>meant</b> to be running,
  180. * for example if the icon is not visible but would be animated if it was.
  181. * </p>
  182. * @return <code>true</code> if the animation is currently running, <code>false</code>.
  183. */
  184. public synchronized boolean isAnimated() {return animate;}
  185. /**
  186. * Returns the number of milliseconds the animation will sleep between each frame.
  187. * @return the number of milliseconds the animation will sleep between each frame.
  188. */
  189. public synchronized int getFrameDelay() {return timer.getDelay();}
  190. // - Painting ------------------------------------------------------------------------
  191. // -----------------------------------------------------------------------------------
  192. /**
  193. * Paints the icon's current frame.
  194. * @param c component in which to paint the icon.
  195. * @param g graphic context in which to paint the icon.
  196. * @param x horizontal coordinate at which to paint the icon.
  197. * @param y vertical coordinate at which to paint the icon.
  198. */
  199. public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
  200. // Paints the current frame.
  201. paintFrame(c, g, x, y);
  202. // Stores the component and starts / restarts the timer if necessary.
  203. if(c != null) {
  204. AffineTransform transform;
  205. transform = ((Graphics2D)g).getTransform();
  206. components.add(new TrackedComponent(c, x, y, (int)(getIconWidth() * transform.getScaleX()), (int)(getIconHeight() * transform.getScaleY())));
  207. // Restarts the timer if necessary.
  208. if(!timer.isRunning() && animate)
  209. timer.restart();
  210. }
  211. }
  212. /**
  213. * Forces the icon to repaint.
  214. */
  215. protected synchronized void repaint() {
  216. // If the component list is empty, we can stop the timer.
  217. if(components.isEmpty())
  218. timer.stop();
  219. // Repaints all pending components.
  220. else {
  221. for(TrackedComponent comp : components)
  222. comp.repaint();
  223. components.clear();
  224. }
  225. }
  226. @Override
  227. protected void finalize() throws Throwable {
  228. // Forces the timer to stop when the animation isn't used anymore.
  229. timer.stop();
  230. super.finalize();
  231. }
  232. // - Container tracking --------------------------------------------------------------
  233. // -----------------------------------------------------------------------------------
  234. /**
  235. * Used to keep track of the various components in which an animated icon is being painted.
  236. * @author twall, Nicolas Rinaudo
  237. */
  238. private static class TrackedComponent {
  239. /** Component in which the icon must be painted. */
  240. private Component component;
  241. /** Horizontal coordinate at which the icon should be painted. */
  242. private int x;
  243. /** Vertical coordinate at which the icon should be painted. */
  244. private int y;
  245. /** Width of the icon (used for clipping). */
  246. private int width;
  247. /** Height of the icon (used for clipping). */
  248. private int height;
  249. /** Component's hashcode. */
  250. private int hashCode;
  251. /**
  252. * Creates a new tracked component.
  253. * @param c component in which to paint the icon.
  254. * @param x horizontal coordinate at which to paint the icon.
  255. * @param y vertical coordinate at which to paint the icon.
  256. * @param width width of the icon.
  257. * @param height height of the icon.
  258. */
  259. public TrackedComponent(Component c, int x, int y, int width, int height) {
  260. Component ancestor;
  261. // Identifies the component that displays the icon.
  262. if((ancestor = findNonRendererAncestor(c)) != c) {
  263. Point pt = SwingUtilities.convertPoint(c, x, y, ancestor);
  264. c = ancestor;
  265. x = pt.x;
  266. y = pt.y;
  267. }
  268. // Stores all the necessary information and computes the tracked component's hashcode.
  269. component = c;
  270. this.x = x;
  271. this.y = y;
  272. this.width = width;
  273. this.height = height;
  274. hashCode = (x + "," + y + ":" + c.hashCode()).hashCode();
  275. }
  276. public int hashCode() {return hashCode;}
  277. /**
  278. * Finds the specified component's first non-renderer ancestor.
  279. * @param c component whose ancestors should be explored.
  280. */
  281. private Component findNonRendererAncestor(Component c) {
  282. Component ancestor;
  283. ancestor = SwingUtilities.getAncestorOfClass(CellRendererPane.class, c);
  284. if (ancestor != null && ancestor != c && ancestor.getParent() != null)
  285. c = findNonRendererAncestor(ancestor.getParent());
  286. return c;
  287. }
  288. /**
  289. * Forces the tracked component to repaint the animated icon.
  290. */
  291. public void repaint() {component.repaint(x, y, width, height);}
  292. }
  293. // - Timer management ----------------------------------------------------------------
  294. // -----------------------------------------------------------------------------------
  295. /**
  296. * Receives timer events and notifies the icon.
  297. * @author twall, Nicolas Rinaudo
  298. */
  299. private static class AnimationUpdater implements ActionListener {
  300. /** Weak reference to the animation. */
  301. private WeakReference<AnimatedIcon> icon;
  302. /**
  303. * Creates a new animation updater on the specified icon.
  304. * @param icon animation to update.
  305. */
  306. public AnimationUpdater(AnimatedIcon icon) {this.icon = new WeakReference<AnimatedIcon>(icon);}
  307. /**
  308. * Notifies the icon that it should update.
  309. * @param event ignored.
  310. */
  311. public void actionPerformed(ActionEvent event) {
  312. AnimatedIcon i;
  313. // Makes sure the animation hasn't been garbage collected.
  314. if((i = icon.get()) != null)
  315. i.nextFrame();
  316. }
  317. }
  318. }