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