/** Copyright (c) 2005 Timothy Wall, All Rights Reserved. Permission granted for any use, this notice must not be removed or modified. twall@users.sf.net */ package org.jgraph.example; import java.awt.*; import java.awt.image.*; import java.util.*; import java.util.Map; import javax.swing.*; /** * Provides animated {@link ImageIcon}s for <code>Component</code> s which * have CellRenderer-like functionality. Ordinarily, you'd get no animation with * ordinary {@link javax.swing.tree.TreeCellRenderer}or * {@link javax.swing.table.TableCellRenderer}since a single component is used * to paint multiple locations on the <code>Component</code>. Animated icons * must be tracked independently, one per animated cell. This class provides for * maintaining a number of independently animated icons for a given * {@link Component}context. A key must be provided which must be unique across * all elements of substructure (a table might provide "row x col", while a tree * might provide the actual value in a row). * <p> * Subclasses should be instantiated near the <code>Component</code> context * and override the {@link #getRepaintRect(Component,Object)}method to trigger * a refresh of its corresponding <code>Component</code> substructure * location. * <p> * Subclasses should take care to invoke {@link #stop()}when you want to stop * the animation on a particular location. * <p> * This class assumes that all operations will take place on the event dispatch * thread, thus there is no class-level synchronization. * * @see javax.swing.tree.TreeCellRenderer * @see javax.swing.tree.DefaultTreeCellRenderer * @see javax.swing.table.TableCellRenderer * @see javax.swing.table.DefaultTableCellRenderer */ // TODO: Figure out how labels update their animation. They don't seem to // use addImageObserver... public abstract class JGraphAbstractIconAnimator { private static final Map contexts = new WeakHashMap(); /** * Return whether the given icon is animated. Currently assumes the original * filename ends with "animated.gif". */ public static boolean isAnimated(Icon icon) { // Kind of a hack... can we see if it's an animated GIF by looking // inside the ImageIcon some other way? if (icon instanceof ImageIcon) { // NOTE: icon.toString() may return null! String label = icon.toString(); if (label != null && label.indexOf("animated.gif") != -1) return true; } return false; } /** * Return any existing, cached animator for the given context/key, or * <code>null</code> if there is none. */ public static JGraphAbstractIconAnimator get(Component context, Object key) { Map map = (Map) contexts.get(context); if (map != null) { return (JGraphAbstractIconAnimator) map.get(key); } return null; } private final ImageIcon icon; private final Component context; private final Object key; /** * Create an object to animate <code>icon</code> on the given * <code>Component</code> at the substructure location represented by the * <code>key</code>. * * @param context * Component on which the animation is to be painted * @param key * Substructure location identifier * @param icon * The animated icon */ public JGraphAbstractIconAnimator(final Component context, final Object key, ImageIcon icon) { this.context = context; this.key = key; // Make a copy, since the original icon may be used repeatedly. this.icon = new ImageIcon(icon.getImage()); this.icon.setImageObserver(new ImageObserver() { public boolean imageUpdate(Image image, int flags, int x, int y, int w, int h) { if ((flags & (FRAMEBITS | ALLBITS)) != 0) { repaint(context, key); } return (flags & (ALLBITS | ABORT)) == 0; } }); Map map = (Map) contexts.get(context); if (map == null) { map = new HashMap(); contexts.put(context, map); } map.put(key, this); } /** * Invoked when the animated icon indicates that it is time for a repaint. * * @param context * Component to refresh * @param key * Substructure identification key */ protected void repaint(Component context, Object key) { Rectangle rect = getRepaintRect(context, key); if (rect != null) { context.repaint(rect.x, rect.y, rect.width, rect.height); } } /** * Based on the <code>Component</code> context and key, return the * <code>Component</code> -relative rectangle to be repainted. */ public abstract Rectangle getRepaintRect(Component context, Object key); /** Stop animating this instance of <code>AbstractIconAnimator</code>. */ public void stop() { icon.setImageObserver(null); icon.getImage().flush(); Map map = (Map) contexts.get(context); if (map != null) { map.remove(key); if (map.size() == 0) contexts.remove(context); } } /** * Returns the icon to be used for the renderer corresponding to this * <code>AbstractIconAnimator</code>. */ public Icon getIcon() { return icon; } }