swingx-master/swingx-core/src/main/java/org/jdesktop/swingx/JXBusyLabel.java
/*
 * $Id$
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.plaf.LabelUI;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.icon.PainterIcon;
import org.jdesktop.swingx.painter.BusyPainter;
import org.jdesktop.swingx.plaf.BusyLabelAddon;
import org.jdesktop.swingx.plaf.BusyLabelUI;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;

/**
 * <p>A simple circular animation, useful for denoting an action is taking
 * place that may take an unknown length of time to complete. Similar to an
 * indeterminant JProgressBar, but with a different look.</p>
 *
 * <p>For example:
 * <pre><code>
 *     JXFrame frame = new JXFrame("test", true);
 *     JXBusyLabel label = new JXBusyLabel();
 *     frame.add(label);
 *     //...
 *     label.setBusy(true);
 * </code></pre></p>
 * Another more complicated example:
 * <pre><code>
 * JXBusyLabel label = new JXBusyLabel(new Dimension(100,84));
 * BusyPainter painter = new BusyPainter(
 * new Rectangle2D.Float(0, 0,13.500001f,1),
 * new RoundRectangle2D.Float(12.5f,12.5f,59.0f,59.0f,10,10));
 * painter.setTrailLength(5);
 * painter.setPoints(31);
 * painter.setFrame(1);
 * label.setPreferredSize(new Dimension(100,84));
 * label.setIcon(new EmptyIcon(100,84));
 * label.setBusyPainter(painter);
 *</code></pre>
 *
 * Another example:
 * <pre><code>
 *     JXBusyLabel label = new MyBusyLabel(new Dimension(100, 84));
 * </code></pre>
 * 
 * where MyBusyLabel is:<br>
 * <pre><code>
 * public class MyBusyLabel extends JXBusyLabel {
 *     public MyBusyLabel(Dimension prefSize) {
 *         super(prefSize);
 *     }
 *     
 *     protected BusyLabel createBusyLabel(Dimension dim) {
 *         BusyPainter painter = new BusyPainter(
 *         new Rectangle2D.Float(0, 0,13.500001f,1),
 *         new RoundRectangle2D.Float(12.5f,12.5f,59.0f,59.0f,10,10));
 *         painter.setTrailLength(5);
 *         painter.setPoints(31);
 *         painter.setFrame(1);
 *         
 *         return painter;
 *     }
 * }
 * </code></pre>
 * 
 * @author rbair
 * @author joshy
 * @author rah003
 * @author headw01
 */
@JavaBean
public class JXBusyLabel extends JLabel {

    private static final long serialVersionUID = 5979268460848257147L;
    private BusyPainter busyPainter;
    private Timer busy;
    private int delay;
    /** Status flag to save/restore status of timer when moving component between containers. */
    private boolean wasBusyOnNotify = false;
    
    /**
     * UI Class ID
     */
    public final static String uiClassID = "BusyLabelUI";

    /**
     * Sets direction of rotation. <code>Direction.RIGHT</code> is the default 
     * value. Direction is taken from the very top point so <code>Direction.RIGHT</code> enables rotation clockwise.
     * @param dir Direction of rotation.
     */
    public void setDirection(BusyPainter.Direction dir) {
        direction = dir;
        getBusyPainter().setDirection(dir);
    }
    
    private BusyPainter.Direction direction;

    /**
     * Creates a default JXLoginPane instance
     */
    static {
        LookAndFeelAddons.contribute(new BusyLabelAddon());
    }

    {
        // Initialize the delay from the UI class.
        BusyLabelUI ui = (BusyLabelUI)getUI();
        if (ui != null) {
            delay = ui.getDelay();
        }
    }
    
    /** Creates a new instance of <code>JXBusyLabel</code> initialized to circular shape in bounds of 26 by 26 points.*/
    public JXBusyLabel() {
        this(null);
    }
    
    /**
     * Creates a new instance of <code>JXBusyLabel</code> initialized to the arbitrary size and using default circular progress indicator.
     * @param dim Preferred size of the label.
     */
    public JXBusyLabel(Dimension dim) {
        super();
        this.setPreferredSize(dim);
        
        // Initialize the BusyPainter.
        getBusyPainter();
    }

    /**
     * Initialize the BusyPainter and (this) JXBusyLabel with the given
     * preferred size.  This method is called automatically when the
     * BusyPainter is set/changed.
     *
     * @param dim The new Preferred Size for the BusyLabel.
     *
     * @see #getBusyPainter()
     * @see #setBusyPainter(BusyPainter)
     */
    protected void initPainter(Dimension dim) {
        BusyPainter busyPainter = getBusyPainter();

        // headw01
        // TODO: Should we force the busyPainter to NOT be cached?
        //       I think we probably should, otherwise the UI will never
        //       be updated after the first paint.
        if (null != busyPainter) {
            busyPainter.setCacheable(false);
        }

        PainterIcon icon = new PainterIcon(dim);
        icon.setPainter(busyPainter);
        this.setIcon(icon);
    }
    /**
     * Create and return a BusyPpainter to use for the Label. This may 
     * be overridden to return any painter you like.  By default, this 
     * method uses the UI (BusyLabelUI)to create a BusyPainter.
     * @param dim Painter size.
     *
     * @see #getUI()
     */
    protected BusyPainter createBusyPainter(Dimension dim) {
        BusyPainter busyPainter = null;
        
        BusyLabelUI ui = (BusyLabelUI)getUI();
        if (ui != null) {
            busyPainter = ui.getBusyPainter(dim);
            
        }
        
        return busyPainter;
    }
    
    /**
     * <p>Gets whether this <code>JXBusyLabel</code> is busy. If busy, then
     * the <code>JXBusyLabel</code> instance will indicate that it is busy,
     * generally by animating some state.</p>
     * 
     * @return true if this instance is busy
     */
    public boolean isBusy() {
        return busy != null;
    }

    /**
     * <p>Sets whether this <code>JXBusyLabel</code> instance should consider
     * itself busy. A busy component may indicate that it is busy via animation,
     * or some other means.</p>
     *
     * @param busy whether this <code>JXBusyLabel</code> instance should
     *        consider itself busy
     */
    public void setBusy(boolean busy) {
        boolean old = isBusy();
        if (!old && busy) {
            startAnimation();
            firePropertyChange("busy", old, isBusy());
        } else if (old && !busy) {
            stopAnimation();
            firePropertyChange("busy", old, isBusy());
        }
    }
    
    private void startAnimation() {
        if(busy != null) {
            stopAnimation();
        }
        
        busy = new Timer(delay, new ActionListener() {
            BusyPainter busyPainter = getBusyPainter();
            int frame = busyPainter.getPoints();
            @Override
            public void actionPerformed(ActionEvent e) {
                frame = (frame+1)%busyPainter.getPoints();
                busyPainter.setFrame(direction == BusyPainter.Direction.LEFT ? busyPainter.getPoints() - frame : frame);
                frameChanged();
            }
        });
        busy.start();
    }
    
    
    
    
    private void stopAnimation() {
        if (busy != null) {
            busy.stop();
            getBusyPainter().setFrame(-1);
            repaint();
            busy = null;
        }
    }
    
    @Override
    public void removeNotify() {
        // fix for #698
        wasBusyOnNotify = isBusy();
        // fix for #626
        stopAnimation();
        super.removeNotify();
    }
    
    @Override
    public void addNotify() {
        super.addNotify();
        // fix for #698
        if (wasBusyOnNotify) {
            // fix for #626
            startAnimation();
        }
    }

    protected void frameChanged() {
        repaint();
    }

    /**
     * Returns the current BusyPainter.  If no BusyPainter is currently
     * set on this BusyLabel, the {@link #createBusyPainter(Dimension)} 
     * method is called to create one.  Afterwards, 
     * {@link #initPainter(Dimension)} is called to update the BusyLabel
     * with the created BusyPainter.
     *
     * @return the busyPainter
     *
     * @see #createBusyPainter(Dimension)
     * @see #initPainter(Dimension)
     */
    public final BusyPainter getBusyPainter() {
        if (null == busyPainter) {
            Dimension prefSize = getPreferredSize();

            busyPainter = createBusyPainter((prefSize.width == 0 && prefSize.height == 0 && !isPreferredSizeSet()) ? null : prefSize);
            
            if (null != busyPainter) {
                if (!isPreferredSizeSet() && (null == prefSize || prefSize.width == 0 || prefSize.height == 0)) {
                    Rectangle rt = busyPainter.getTrajectory().getBounds();
                    Rectangle rp = busyPainter.getPointShape().getBounds();
                    int max = Math.max(rp.width, rp.height);
                    prefSize = new Dimension(rt.width + max, rt.height + max);
                }
            
                initPainter(prefSize);
            }
        }
        return busyPainter;
    }

    /**
     * @param busyPainter the busyPainter to set
     */
    public final void setBusyPainter(BusyPainter busyPainter) {
        this.busyPainter = busyPainter;
        initPainter(new Dimension(getIcon().getIconWidth(), getIcon().getIconHeight()));
    }

    /**
     * @return the delay
     */
    public int getDelay() {
        return delay;
    }

    /**
     * @param delay the delay to set
     */
    public void setDelay(int delay) {
        int old = getDelay();
        this.delay = delay;
        if (old != getDelay()) {
            if (busy != null && busy.isRunning()) {
                busy.setDelay(getDelay());
            }
            firePropertyChange("delay", old, getDelay());
        }
    }
    //------------------------------------------------------------- UI Logic
    
    /**
     * Notification from the <code>UIManager</code> that the L&F has changed.
     * Replaces the current UI object with the latest version from the
     * <code>UIManager</code>.
     *
     * @see javax.swing.JComponent#updateUI
     */
    @Override
    public void updateUI() {
        setUI((LabelUI) LookAndFeelAddons.getUI(this, BusyLabelUI.class));
    }

    /**
     * Returns the name of the L&F class that renders this component.
     *
     * @return the string {@link #uiClassID}
     * @see javax.swing.JComponent#getUIClassID
     * @see javax.swing.UIDefaults#getUI
     */
    @Override
    public String getUIClassID() {
        return uiClassID;
    }


}

