001/*
002 * $Id: JXBusyLabel.java 4147 2012-02-01 17:13:24Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx;
023
024import java.awt.Dimension;
025import java.awt.Rectangle;
026import java.awt.event.ActionEvent;
027import java.awt.event.ActionListener;
028
029import javax.swing.JLabel;
030import javax.swing.Timer;
031import javax.swing.plaf.LabelUI;
032
033import org.jdesktop.beans.JavaBean;
034import org.jdesktop.swingx.icon.PainterIcon;
035import org.jdesktop.swingx.painter.BusyPainter;
036import org.jdesktop.swingx.plaf.BusyLabelAddon;
037import org.jdesktop.swingx.plaf.BusyLabelUI;
038import org.jdesktop.swingx.plaf.LookAndFeelAddons;
039
040/**
041 * <p>A simple circular animation, useful for denoting an action is taking
042 * place that may take an unknown length of time to complete. Similar to an
043 * indeterminant JProgressBar, but with a different look.</p>
044 *
045 * <p>For example:
046 * <pre><code>
047 *     JXFrame frame = new JXFrame("test", true);
048 *     JXBusyLabel label = new JXBusyLabel();
049 *     frame.add(label);
050 *     //...
051 *     label.setBusy(true);
052 * </code></pre></p>
053 * Another more complicated example:
054 * <pre><code>
055 * JXBusyLabel label = new JXBusyLabel(new Dimension(100,84));
056 * BusyPainter painter = new BusyPainter(
057 * new Rectangle2D.Float(0, 0,13.500001f,1),
058 * new RoundRectangle2D.Float(12.5f,12.5f,59.0f,59.0f,10,10));
059 * painter.setTrailLength(5);
060 * painter.setPoints(31);
061 * painter.setFrame(1);
062 * label.setPreferredSize(new Dimension(100,84));
063 * label.setIcon(new EmptyIcon(100,84));
064 * label.setBusyPainter(painter);
065 *</code></pre>
066 *
067 * Another example:
068 * <pre><code>
069 *     JXBusyLabel label = new MyBusyLabel(new Dimension(100, 84));
070 * </code></pre>
071 * 
072 * where MyBusyLabel is:<br>
073 * <pre><code>
074 * public class MyBusyLabel extends JXBusyLabel {
075 *     public MyBusyLabel(Dimension prefSize) {
076 *         super(prefSize);
077 *     }
078 *     
079 *     protected BusyLabel createBusyLabel(Dimension dim) {
080 *         BusyPainter painter = new BusyPainter(
081 *         new Rectangle2D.Float(0, 0,13.500001f,1),
082 *         new RoundRectangle2D.Float(12.5f,12.5f,59.0f,59.0f,10,10));
083 *         painter.setTrailLength(5);
084 *         painter.setPoints(31);
085 *         painter.setFrame(1);
086 *         
087 *         return painter;
088 *     }
089 * }
090 * </code></pre>
091 * 
092 * @author rbair
093 * @author joshy
094 * @author rah003
095 * @author headw01
096 */
097@JavaBean
098public class JXBusyLabel extends JLabel {
099
100    private static final long serialVersionUID = 5979268460848257147L;
101    private BusyPainter busyPainter;
102    private Timer busy;
103    private int delay;
104    /** Status flag to save/restore status of timer when moving component between containers. */
105    private boolean wasBusyOnNotify = false;
106    
107    /**
108     * UI Class ID
109     */
110    public final static String uiClassID = "BusyLabelUI";
111
112    /**
113     * Sets direction of rotation. <code>Direction.RIGHT</code> is the default 
114     * value. Direction is taken from the very top point so <code>Direction.RIGHT</code> enables rotation clockwise.
115     * @param dir Direction of rotation.
116     */
117    public void setDirection(BusyPainter.Direction dir) {
118        direction = dir;
119        getBusyPainter().setDirection(dir);
120    }
121    
122    private BusyPainter.Direction direction;
123
124    /**
125     * Creates a default JXLoginPane instance
126     */
127    static {
128        LookAndFeelAddons.contribute(new BusyLabelAddon());
129    }
130
131    {
132        // Initialize the delay from the UI class.
133        BusyLabelUI ui = (BusyLabelUI)getUI();
134        if (ui != null) {
135            delay = ui.getDelay();
136        }
137    }
138    
139    /** Creates a new instance of <code>JXBusyLabel</code> initialized to circular shape in bounds of 26 by 26 points.*/
140    public JXBusyLabel() {
141        this(null);
142    }
143    
144    /**
145     * Creates a new instance of <code>JXBusyLabel</code> initialized to the arbitrary size and using default circular progress indicator.
146     * @param dim Preferred size of the label.
147     */
148    public JXBusyLabel(Dimension dim) {
149        super();
150        this.setPreferredSize(dim);
151        
152        // Initialize the BusyPainter.
153        getBusyPainter();
154    }
155
156    /**
157     * Initialize the BusyPainter and (this) JXBusyLabel with the given
158     * preferred size.  This method is called automatically when the
159     * BusyPainter is set/changed.
160     *
161     * @param dim The new Preferred Size for the BusyLabel.
162     *
163     * @see #getBusyPainter()
164     * @see #setBusyPainter(BusyPainter)
165     */
166    protected void initPainter(Dimension dim) {
167        BusyPainter busyPainter = getBusyPainter();
168
169        // headw01
170        // TODO: Should we force the busyPainter to NOT be cached?
171        //       I think we probably should, otherwise the UI will never
172        //       be updated after the first paint.
173        if (null != busyPainter) {
174            busyPainter.setCacheable(false);
175        }
176
177        PainterIcon icon = new PainterIcon(dim);
178        icon.setPainter(busyPainter);
179        this.setIcon(icon);
180    }
181    /**
182     * Create and return a BusyPpainter to use for the Label. This may 
183     * be overridden to return any painter you like.  By default, this 
184     * method uses the UI (BusyLabelUI)to create a BusyPainter.
185     * @param dim Painter size.
186     *
187     * @see #getUI()
188     */
189    protected BusyPainter createBusyPainter(Dimension dim) {
190        BusyPainter busyPainter = null;
191        
192        BusyLabelUI ui = (BusyLabelUI)getUI();
193        if (ui != null) {
194            busyPainter = ui.getBusyPainter(dim);
195            
196        }
197        
198        return busyPainter;
199    }
200    
201    /**
202     * <p>Gets whether this <code>JXBusyLabel</code> is busy. If busy, then
203     * the <code>JXBusyLabel</code> instance will indicate that it is busy,
204     * generally by animating some state.</p>
205     * 
206     * @return true if this instance is busy
207     */
208    public boolean isBusy() {
209        return busy != null;
210    }
211
212    /**
213     * <p>Sets whether this <code>JXBusyLabel</code> instance should consider
214     * itself busy. A busy component may indicate that it is busy via animation,
215     * or some other means.</p>
216     *
217     * @param busy whether this <code>JXBusyLabel</code> instance should
218     *        consider itself busy
219     */
220    public void setBusy(boolean busy) {
221        boolean old = isBusy();
222        if (!old && busy) {
223            startAnimation();
224            firePropertyChange("busy", old, isBusy());
225        } else if (old && !busy) {
226            stopAnimation();
227            firePropertyChange("busy", old, isBusy());
228        }
229    }
230    
231    private void startAnimation() {
232        if(busy != null) {
233            stopAnimation();
234        }
235        
236        busy = new Timer(delay, new ActionListener() {
237            BusyPainter busyPainter = getBusyPainter();
238            int frame = busyPainter.getPoints();
239            @Override
240            public void actionPerformed(ActionEvent e) {
241                frame = (frame+1)%busyPainter.getPoints();
242                busyPainter.setFrame(direction == BusyPainter.Direction.LEFT ? busyPainter.getPoints() - frame : frame);
243                frameChanged();
244            }
245        });
246        busy.start();
247    }
248    
249    
250    
251    
252    private void stopAnimation() {
253        if (busy != null) {
254            busy.stop();
255            getBusyPainter().setFrame(-1);
256            repaint();
257            busy = null;
258        }
259    }
260    
261    @Override
262    public void removeNotify() {
263        // fix for #698
264        wasBusyOnNotify = isBusy();
265        // fix for #626
266        stopAnimation();
267        super.removeNotify();
268    }
269    
270    @Override
271    public void addNotify() {
272        super.addNotify();
273        // fix for #698
274        if (wasBusyOnNotify) {
275            // fix for #626
276            startAnimation();
277        }
278    }
279
280    protected void frameChanged() {
281        repaint();
282    }
283
284    /**
285     * Returns the current BusyPainter.  If no BusyPainter is currently
286     * set on this BusyLabel, the {@link #createBusyPainter(Dimension)} 
287     * method is called to create one.  Afterwards, 
288     * {@link #initPainter(Dimension)} is called to update the BusyLabel
289     * with the created BusyPainter.
290     *
291     * @return the busyPainter
292     *
293     * @see #createBusyPainter(Dimension)
294     * @see #initPainter(Dimension)
295     */
296    public final BusyPainter getBusyPainter() {
297        if (null == busyPainter) {
298            Dimension prefSize = getPreferredSize();
299
300            busyPainter = createBusyPainter((prefSize.width == 0 && prefSize.height == 0 && !isPreferredSizeSet()) ? null : prefSize);
301            
302            if (null != busyPainter) {
303                if (!isPreferredSizeSet() && (null == prefSize || prefSize.width == 0 || prefSize.height == 0)) {
304                    Rectangle rt = busyPainter.getTrajectory().getBounds();
305                    Rectangle rp = busyPainter.getPointShape().getBounds();
306                    int max = Math.max(rp.width, rp.height);
307                    prefSize = new Dimension(rt.width + max, rt.height + max);
308                }
309            
310                initPainter(prefSize);
311            }
312        }
313        return busyPainter;
314    }
315
316    /**
317     * @param busyPainter the busyPainter to set
318     */
319    public final void setBusyPainter(BusyPainter busyPainter) {
320        this.busyPainter = busyPainter;
321        initPainter(new Dimension(getIcon().getIconWidth(), getIcon().getIconHeight()));
322    }
323
324    /**
325     * @return the delay
326     */
327    public int getDelay() {
328        return delay;
329    }
330
331    /**
332     * @param delay the delay to set
333     */
334    public void setDelay(int delay) {
335        int old = getDelay();
336        this.delay = delay;
337        if (old != getDelay()) {
338            if (busy != null && busy.isRunning()) {
339                busy.setDelay(getDelay());
340            }
341            firePropertyChange("delay", old, getDelay());
342        }
343    }
344    //------------------------------------------------------------- UI Logic
345    
346    /**
347     * Notification from the <code>UIManager</code> that the L&F has changed.
348     * Replaces the current UI object with the latest version from the
349     * <code>UIManager</code>.
350     *
351     * @see javax.swing.JComponent#updateUI
352     */
353    @Override
354    public void updateUI() {
355        setUI((LabelUI) LookAndFeelAddons.getUI(this, BusyLabelUI.class));
356    }
357
358    /**
359     * Returns the name of the L&F class that renders this component.
360     *
361     * @return the string {@link #uiClassID}
362     * @see javax.swing.JComponent#getUIClassID
363     * @see javax.swing.UIDefaults#getUI
364     */
365    @Override
366    public String getUIClassID() {
367        return uiClassID;
368    }
369
370
371}
372