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