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