Source for javax.swing.ProgressMonitor

   1: /* ProgressMonitor.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package javax.swing;
  39: 
  40: import java.awt.Component;
  41: import java.awt.event.ActionListener;
  42: import java.awt.event.ActionEvent;
  43: 
  44: /**
  45:  * <p>Using this class you can easily monitor tasks where you cannot
  46:  * estimate the duration exactly.</p>
  47:  *
  48:  * <p>A ProgressMonitor instance waits until the first time setProgress
  49:  * is called. When <code>millisToDecideToPopup</code> time elapsed the
  50:  * instance estimates the duration until the whole operation is completed.
  51:  * If this duration exceeds <code>millisToPopup</code> a non-modal dialog
  52:  * with a message and a progress bar is shown.</p>
  53:  *
  54:  * <p>The value of <code>millisToDecideToPopup</code> defaults to
  55:  * <code>500</code> and <code>millisToPopup</code> to
  56:  * <code>2000</code>.</p>
  57:  *
  58:  * @author Andrew Selkirk
  59:  * @author Robert Schuster (robertschuster@fsfe.org)
  60:  * @since 1.2
  61:  * @status updated to 1.2
  62:  */
  63: public class ProgressMonitor
  64: {
  65:   /**
  66:    * parentComponent
  67:    */
  68:   Component component;
  69: 
  70:   /**
  71:    * note
  72:    */
  73:   String note;
  74: 
  75:   /**
  76:    * message
  77:    */
  78:   Object message;
  79: 
  80:   /**
  81:    * millisToDecideToPopup
  82:    */
  83:   int millisToDecideToPopup = 500;
  84: 
  85:   /**
  86:    * millisToPopup
  87:    */
  88:   int millisToPopup = 2000;
  89: 
  90:   int min, max, progress;
  91: 
  92:   JProgressBar progressBar;
  93: 
  94:   JLabel noteLabel;
  95: 
  96:   JDialog progressDialog;
  97: 
  98:   Timer timer;
  99: 
 100:   boolean canceled;
 101: 
 102:   /**
 103:    * Creates a new <code>ProgressMonitor</code> instance.  This is used to 
 104:    * monitor a task and pops up a dialog if the task is taking a long time to 
 105:    * run.
 106:    * 
 107:    * @param component The parent component of the progress dialog or 
 108:    *                  <code>null</code>.
 109:    * @param message A constant message object which works in the way it does 
 110:    *                in {@link JOptionPane}.
 111:    * @param note A string message which can be changed while the operation goes
 112:    *             on.
 113:    * @param minimum The minimum value for the operation (start value).
 114:    * @param maximum The maximum value for the operation (end value).
 115:    */
 116:   public ProgressMonitor(Component component, Object message,
 117:                          String note, int minimum, int maximum)
 118:   {
 119: 
 120:     // Set data.
 121:     this.component = component;
 122:     this.message = message;
 123:     this.note = note;
 124: 
 125:     min = minimum;
 126:     max = maximum;
 127:   }
 128: 
 129:   /**
 130:    * <p>Hides the dialog and stops any measurements.</p>
 131:    *
 132:    * <p>Has no effect when <code>setProgress</code> is not at least
 133:    * called once.</p>
 134:    */
 135:   public void close()
 136:   {
 137:     if ( progressDialog != null )
 138:       {
 139:         progressDialog.setVisible(false);
 140:       }
 141: 
 142:     if ( timer != null )
 143:       {
 144:         timer.stop();
 145:         timer = null;
 146:       }
 147:   }
 148: 
 149:   /**
 150:    * <p>Updates the progress value.</p>
 151:    *
 152:    * <p>When called for the first time this initializes a timer
 153:    * which decides after <code>millisToDecideToPopup</code> time
 154:    * whether to show a progress dialog or not.</p>
 155:    *
 156:    * <p>If the progress value equals or exceeds the maximum
 157:    * value the progress dialog is closed automatically.</p>
 158:    *
 159:    * @param progress New progress value.
 160:    */
 161:   public void setProgress(int progress)
 162:   {
 163:     this.progress = progress;
 164: 
 165:     // Initializes and starts a timer with a task
 166:     // which measures the duration and displays
 167:     // a progress dialog if neccessary.
 168:     if ( timer == null && progressDialog == null )
 169:       {
 170:         timer = new Timer(25, null);
 171:         timer.addActionListener(new TimerListener());
 172:         timer.start();
 173:       }
 174: 
 175:     // Cancels timer and hides progress dialog if the
 176:     // maximum value is reached.
 177:     if ( progressBar != null && this.progress >= progressBar.getMaximum() )
 178:       {
 179:         // The reason for using progressBar.getMaximum() instead of max is that
 180:         // we want to prevent that changes to the value have any effect after the
 181:         // progress dialog is visible (This is how the JDK behaves.).
 182:         close();
 183:       }
 184: 
 185:   }
 186: 
 187:   /** 
 188:    * Returns the minimum or start value of the operation.
 189:    *
 190:    * @return Minimum or start value of the operation.
 191:    */
 192:   public int getMinimum()
 193:   {
 194:     return min;
 195:   }
 196: 
 197:   /**
 198:    * <p>Use this method to set the minimum or start value of
 199:    * your operation.</p>
 200:    *
 201:    * <p>For typical application like copy operation this will be
 202:    * zero.</p>
 203:    *
 204:    * <p>Keep in mind that changing this value after the progress
 205:    * dialog is made visible has no effect upon the progress bar.</p>
 206:    *
 207:    * @param minimum The new minimum value.
 208:    */
 209:   public void setMinimum(int minimum)
 210:   {
 211:     min = minimum;
 212:   }
 213: 
 214:   /**
 215:    * Return the maximum or end value of your operation.
 216:    *
 217:    * @return Maximum or end value.
 218:    */
 219:   public int getMaximum()
 220:   {
 221:     return max;
 222:   }
 223: 
 224:   /**
 225:    * <p>Sets the maximum or end value of the operation to the
 226:    * given integer.</p>
 227:    *
 228:    * @param maximum
 229:    */
 230:   public void setMaximum(int maximum)
 231:   {
 232:     max = maximum;
 233:   }
 234: 
 235:   /**
 236:    * Returns whether the user canceled the operation.
 237:    *
 238:    * @return Whether the operation was canceled.
 239:    */
 240:   public boolean isCanceled()
 241:   {
 242:     // The value is predefined to false
 243:     // and changes only when the user clicks
 244:     // the cancel button in the progress dialog.
 245:     return canceled;
 246:   }
 247: 
 248:   /**
 249:    * Returns the amount of milliseconds to wait
 250:    * until the ProgressMonitor should decide whether
 251:    * a progress dialog is to be shown or not.
 252:    *
 253:    * @return The duration in milliseconds.
 254:    */
 255:   public int getMillisToDecideToPopup()
 256:   {
 257:     return millisToDecideToPopup;
 258:   }
 259: 
 260:   /**
 261:    * Sets the amount of milliseconds to wait until the
 262:    * ProgressMonitor should decide whether a progress dialog
 263:    * is to be shown or not.
 264:    *
 265:    * <p>This method has no effect when the progress dialog
 266:    * is already visible.</p>
 267:    *
 268:    * @param time The duration in milliseconds.
 269:    */
 270:   public void setMillisToDecideToPopup(int time)
 271:   {
 272:     millisToDecideToPopup = time;
 273:   }
 274: 
 275:   /**
 276:    * Returns the number of milliseconds to wait before displaying the progress
 277:    * dialog.  The default value is 2000.
 278:    * 
 279:    * @return The number of milliseconds.
 280:    * 
 281:    * @see #setMillisToPopup(int)
 282:    */
 283:   public int getMillisToPopup()
 284:   {
 285:     return millisToPopup;
 286:   }
 287: 
 288:   /**
 289:    * Sets the number of milliseconds to wait before displaying the progress
 290:    * dialog.
 291:    * 
 292:    * @param time  the number of milliseconds.
 293:    * 
 294:    * @see #getMillisToPopup()
 295:    */
 296:   public void setMillisToPopup(int time)
 297:   {
 298:     millisToPopup = time;
 299:   }
 300: 
 301:   /**
 302:    * Returns a message which is shown in the progress dialog.
 303:    *
 304:    * @return The changeable message visible in the progress dialog.
 305:    */
 306:   public String getNote()
 307:   {
 308:     return note;
 309:   }
 310: 
 311:   /**
 312:    * <p>Set the message shown in the progess dialog.</p>
 313:    *
 314:    * <p>Changing the note while the progress dialog is visible
 315:    * is possible.</p>
 316:    *
 317:    * @param note A message shown in the progress dialog.
 318:    */
 319:   public void setNote(String note)
 320:   {
 321:     if ( noteLabel != null )
 322:       {
 323:         noteLabel.setText(note);
 324:       }
 325:     else
 326:       {
 327:         this.note = note;
 328:       }
 329:   }
 330: 
 331:   /** 
 332:    * Internal method that creates the progress dialog.
 333:    */
 334:   void createDialog()
 335:   {
 336:     // If there is no note we suppress the generation of the
 337:     // label.
 338:     Object[] tmp = (note == null) ?
 339:       new Object[]
 340:         {
 341:           message,
 342:           progressBar = new JProgressBar(min, max)
 343:         }
 344:       :
 345:       new Object[]
 346:         {
 347:           message,
 348:           noteLabel = new JLabel(note),
 349:           progressBar = new JProgressBar(min, max)
 350:         };
 351: 
 352:     JOptionPane pane = new JOptionPane(tmp, JOptionPane.INFORMATION_MESSAGE);
 353: 
 354:     // FIXME: Internationalize the button
 355:     JButton cancelButton = new JButton("Cancel");
 356:     cancelButton.addActionListener(new ActionListener()
 357:     {
 358:       public void actionPerformed(ActionEvent ae)
 359:       {
 360:         canceled = true;
 361:       }
 362:     });
 363: 
 364:     pane.setOptions(new Object[] { cancelButton });
 365: 
 366:     // FIXME: Internationalize the title
 367:     progressDialog = pane.createDialog(component, "Progress ...");
 368:     progressDialog.setModal(false);
 369:     progressDialog.setResizable(true);
 370: 
 371:     progressDialog.pack();
 372:     progressDialog.setVisible(true);
 373: 
 374:   }
 375: 
 376:   /** An ActionListener implementation which does the measurements
 377:    * and estimations of the ProgressMonitor.
 378:    */
 379:   class TimerListener implements ActionListener
 380:   {
 381:     long timestamp;
 382: 
 383:     int lastProgress;
 384: 
 385:     boolean first = true;
 386: 
 387:     TimerListener()
 388:     {
 389:        timestamp = System.currentTimeMillis();
 390:     }
 391: 
 392:     public void actionPerformed(ActionEvent ae)
 393:     {
 394:        long now = System.currentTimeMillis();
 395: 
 396:        if ( first )
 397:        {
 398:          if (( now - timestamp ) > millisToDecideToPopup )
 399:          {
 400:            first = false;
 401: 
 402: 
 403:            long expected = ( progress - min == 0 ) ? 
 404:          ( now - timestamp ) * ( max - min ) : 
 405:          ( now - timestamp ) * ( max - min ) / ( progress - min );
 406: 
 407:            if ( expected > millisToPopup )
 408:            {
 409:              createDialog();
 410:            }
 411:          }
 412:          else
 413:          {
 414:            // We have not waited long enough to make a decision,
 415:            // so return and try again when the timer is invoked.
 416:            return;
 417:          }
 418:        }
 419:        else if ( progressDialog != null )
 420:        {
 421:          // The progress dialog is being displayed. We now calculate
 422:          // whether setting the progress bar to the current progress
 423:          // value would result in a visual difference. 
 424:          int delta = progress - progressBar.getValue();
 425: 
 426:          if ( ( delta * progressBar.getWidth() / (max - min) ) > 0 )
 427:          {
 428:            // At least one pixel would change.
 429:            progressBar.setValue(progress);
 430:          }
 431:        }
 432:        else
 433:        {
 434:          // No dialog necessary
 435:          timer.stop();
 436:          timer = null;
 437:        }
 438: 
 439:       timestamp = now;
 440:     }
 441:   }
 442: 
 443: }