Source for javax.swing.JSpinner

   1: /* JSpinner.java --
   2:    Copyright (C) 2004, 2005  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: 
  39: package javax.swing;
  40: 
  41: import java.awt.Component;
  42: import java.awt.Container;
  43: import java.awt.Dimension;
  44: import java.awt.Insets;
  45: import java.awt.LayoutManager;
  46: import java.beans.PropertyChangeEvent;
  47: import java.beans.PropertyChangeListener;
  48: import java.text.DecimalFormat;
  49: import java.text.ParseException;
  50: import java.text.SimpleDateFormat;
  51: 
  52: import javax.swing.event.ChangeEvent;
  53: import javax.swing.event.ChangeListener;
  54: import javax.swing.plaf.SpinnerUI;
  55: import javax.swing.text.DateFormatter;
  56: 
  57: /**
  58:  * A JSpinner is a component which typically contains a numeric value and a
  59:  * way to manipulate the value.
  60:  *
  61:  * @author Ka-Hing Cheung
  62:  * 
  63:  * @since 1.4
  64:  */
  65: public class JSpinner extends JComponent
  66: {
  67:   /**
  68:    * DOCUMENT ME!
  69:    */
  70:   public static class DefaultEditor extends JPanel implements ChangeListener,
  71:                                                               PropertyChangeListener,
  72:                                                               LayoutManager
  73:   {
  74:     private JSpinner spinner;
  75: 
  76:     /** The JFormattedTextField that backs the editor. */
  77:     JFormattedTextField ftf;
  78: 
  79:     /**
  80:      * For compatability with Sun's JDK 1.4.2 rev. 5
  81:      */
  82:     private static final long serialVersionUID = -5317788736173368172L;
  83: 
  84:     /**
  85:      * Creates a new <code>DefaultEditor</code> object.
  86:      *
  87:      * @param spinner the <code>JSpinner</code> associated with this editor
  88:      */
  89:     public DefaultEditor(JSpinner spinner)
  90:     {
  91:       super();
  92:       setLayout(this);
  93:       this.spinner = spinner;
  94:       ftf = new JFormattedTextField();
  95:       add(ftf);
  96:       ftf.setValue(spinner.getValue());
  97:       spinner.addChangeListener(this);
  98:     }
  99: 
 100:     /**
 101:      * Returns the <code>JSpinner</code> object for this editor.
 102:      */
 103:     public JSpinner getSpinner()
 104:     {
 105:       return spinner;
 106:     }
 107:     
 108:     /**
 109:      * DOCUMENT ME!
 110:      */
 111:     public void commitEdit()
 112:       throws ParseException
 113:     {
 114:     } /* TODO */
 115: 
 116:     /**
 117:      * DOCUMENT ME!
 118:      *
 119:      * @param spinner DOCUMENT ME!
 120:      */
 121:     public void dismiss(JSpinner spinner)
 122:     {
 123:       spinner.removeChangeListener(this);
 124:     }
 125: 
 126:     /**
 127:      * DOCUMENT ME!
 128:      *
 129:      * @return DOCUMENT ME!
 130:      */
 131:     public JFormattedTextField getTextField()
 132:     {
 133:       return ftf;
 134:     }
 135:     
 136:     /**
 137:      * DOCUMENT ME!
 138:      *
 139:      * @param parent DOCUMENT ME!
 140:      */
 141:     public void layoutContainer(Container parent)
 142:     {
 143:       Insets insets = getInsets();
 144:       Dimension size = getSize();
 145:       ftf.setBounds(insets.left, insets.top,
 146:                     size.width - insets.left - insets.right,
 147:                     size.height - insets.top - insets.bottom);
 148:     }
 149:     
 150:     /**
 151:      * DOCUMENT ME!
 152:      *
 153:      * @param parent DOCUMENT ME!
 154:      *
 155:      * @return DOCUMENT ME!
 156:      */
 157:     public Dimension minimumLayoutSize(Container parent)
 158:     {
 159:       Insets insets = getInsets();
 160:       Dimension minSize = ftf.getMinimumSize();
 161:       return new Dimension(minSize.width + insets.left + insets.right,
 162:                             minSize.height + insets.top + insets.bottom);
 163:     }
 164:     
 165:     /**
 166:      * DOCUMENT ME!
 167:      *
 168:      * @param parent DOCUMENT ME!
 169:      *
 170:      * @return DOCUMENT ME!
 171:      */
 172:     public Dimension preferredLayoutSize(Container parent)
 173:     {
 174:       Insets insets = getInsets();
 175:       Dimension prefSize = ftf.getPreferredSize();
 176:       return new Dimension(prefSize.width + insets.left + insets.right,
 177:                             prefSize.height + insets.top + insets.bottom);
 178:     }
 179:     
 180:     /**
 181:      * DOCUMENT ME!
 182:      *
 183:      * @param event DOCUMENT ME!
 184:      */
 185:     public void propertyChange(PropertyChangeEvent event)
 186:     {
 187:     } /* TODO */
 188:     
 189:     /**
 190:      * DOCUMENT ME!
 191:      *
 192:      * @param event DOCUMENT ME!
 193:      */
 194:     public void stateChanged(ChangeEvent event)
 195:     {
 196:     } /* TODO */
 197:     
 198:     /* no-ops */
 199:     public void removeLayoutComponent(Component child)
 200:     {
 201:     }
 202: 
 203:     /**
 204:      * DOCUMENT ME!
 205:      *
 206:      * @param name DOCUMENT ME!
 207:      * @param child DOCUMENT ME!
 208:      */
 209:     public void addLayoutComponent(String name, Component child)
 210:     {
 211:     }
 212:   }
 213: 
 214:   /**
 215:    * DOCUMENT ME!
 216:    */
 217:   public static class NumberEditor extends DefaultEditor
 218:   {
 219:     /**
 220:      * For compatability with Sun's JDK
 221:      */
 222:     private static final long serialVersionUID = 3791956183098282942L;
 223: 
 224:     /**
 225:      * Creates a new NumberEditor object.
 226:      *
 227:      * @param spinner DOCUMENT ME!
 228:      */
 229:     public NumberEditor(JSpinner spinner)
 230:     {
 231:       super(spinner);
 232:     }
 233: 
 234:     /**
 235:      * Creates a new NumberEditor object.
 236:      *
 237:      * @param spinner DOCUMENT ME!
 238:      */
 239:     public NumberEditor(JSpinner spinner, String decimalFormatPattern)
 240:     {
 241:       super(spinner);
 242:     }
 243: 
 244:     /**
 245:      * DOCUMENT ME!
 246:      *
 247:      * @return DOCUMENT ME!
 248:      */
 249:     public DecimalFormat getFormat()
 250:     {
 251:       return null;
 252:     }
 253: 
 254:     public SpinnerNumberModel getModel()
 255:     {
 256:       return (SpinnerNumberModel) getSpinner().getModel();
 257:     }
 258:   }
 259: 
 260:   /**
 261:    * An editor class for a <code>JSpinner</code> that is used
 262:    * for displaying and editing dates (e.g. that uses
 263:    * <code>SpinnerDateModel</code> as model).
 264:    *
 265:    * The editor uses a {@link JTextField} with the value
 266:    * displayed by a {@link DateFormatter} instance.
 267:    */
 268:   public static class DateEditor extends DefaultEditor
 269:   {
 270: 
 271:     /** The serialVersionUID. */
 272:     private static final long serialVersionUID = -4279356973770397815L;
 273: 
 274:     /** The DateFormat instance used to format the date. */
 275:     SimpleDateFormat dateFormat;
 276: 
 277:     /**
 278:      * Creates a new instance of DateEditor for the specified
 279:      * <code>JSpinner</code>.
 280:      *
 281:      * @param spinner the <code>JSpinner</code> for which to
 282:      *     create a <code>DateEditor</code> instance
 283:      */
 284:     public DateEditor(JSpinner spinner)
 285:     {
 286:       super(spinner);
 287:       init(new SimpleDateFormat());
 288:     }
 289: 
 290:     /**
 291:      * Creates a new instance of DateEditor for the specified
 292:      * <code>JSpinner</code> using the specified date format
 293:      * pattern.
 294:      *
 295:      * @param spinner the <code>JSpinner</code> for which to
 296:      *     create a <code>DateEditor</code> instance
 297:      * @param dateFormatPattern the date format to use
 298:      *
 299:      * @see SimpleDateFormat#SimpleDateFormat(String)
 300:      */
 301:     public DateEditor(JSpinner spinner, String dateFormatPattern)
 302:     {
 303:       super(spinner);
 304:       init(new SimpleDateFormat(dateFormatPattern));
 305:     }
 306: 
 307:     /**
 308:      * Initializes the JFormattedTextField for this editor.
 309:      *
 310:      * @param the date format to use in the formatted text field
 311:      */
 312:     private void init(SimpleDateFormat format)
 313:     {
 314:       dateFormat = format;
 315:       getTextField().setFormatterFactory(
 316:         new JFormattedTextField.AbstractFormatterFactory()
 317:         {
 318:           public JFormattedTextField.AbstractFormatter
 319:           getFormatter(JFormattedTextField ftf)
 320:           {
 321:             return new DateFormatter(dateFormat);
 322:           }
 323:         });
 324:     }
 325: 
 326:     /**
 327:      * Returns the <code>SimpleDateFormat</code> instance that is used to
 328:      * format the date value.
 329:      *
 330:      * @return the <code>SimpleDateFormat</code> instance that is used to
 331:      *     format the date value
 332:      */
 333:     public SimpleDateFormat getFormat()
 334:     {
 335:       return dateFormat;
 336:     }
 337: 
 338:     /**
 339:      * Returns the {@link SpinnerDateModel} that is edited by this editor.
 340:      *
 341:      * @return the <code>SpinnerDateModel</code> that is edited by this editor
 342:      */
 343:     public SpinnerDateModel getModel()
 344:     {
 345:       return (SpinnerDateModel) getSpinner().getModel();
 346:     }
 347:   }
 348: 
 349:   private static final long serialVersionUID = 3412663575706551720L;
 350: 
 351:   /** DOCUMENT ME! */
 352:   private SpinnerModel model;
 353: 
 354:   /** DOCUMENT ME! */
 355:   private JComponent editor;
 356: 
 357:   /** DOCUMENT ME! */
 358:   private ChangeListener listener = new ChangeListener()
 359:     {
 360:       public void stateChanged(ChangeEvent evt)
 361:       {
 362:     fireStateChanged();
 363:       }
 364:     };
 365: 
 366:   /**
 367:    * Creates a JSpinner with <code>SpinnerNumberModel</code>
 368:    *
 369:    * @see javax.swing.SpinnerNumberModel
 370:    */
 371:   public JSpinner()
 372:   {
 373:     this(new SpinnerNumberModel());
 374:   }
 375: 
 376:   /**
 377:    * Creates a JSpinner with the specific model and sets the default editor
 378:    *
 379:    * @param model DOCUMENT ME!
 380:    */
 381:   public JSpinner(SpinnerModel model)
 382:   {
 383:     this.model = model;
 384:     model.addChangeListener(listener);
 385:     setEditor(createEditor(model));
 386:     updateUI();
 387:   }
 388: 
 389:   /**
 390:    * If the editor is <code>JSpinner.DefaultEditor</code>, then forwards the
 391:    * call to it, otherwise do nothing.
 392:    *
 393:    * @throws ParseException DOCUMENT ME!
 394:    */
 395:   public void commitEdit() throws ParseException
 396:   {
 397:     if (editor instanceof DefaultEditor)
 398:       ((DefaultEditor) editor).commitEdit();
 399:   }
 400: 
 401:   /**
 402:    * Gets the current editor
 403:    *
 404:    * @return the current editor
 405:    *
 406:    * @see #setEditor
 407:    */
 408:   public JComponent getEditor()
 409:   {
 410:     return editor;
 411:   }
 412: 
 413:   /**
 414:    * Changes the current editor to the new editor. This methods should remove
 415:    * the old listeners (if any) and adds the new listeners (if any).
 416:    *
 417:    * @param editor the new editor
 418:    *
 419:    * @throws IllegalArgumentException DOCUMENT ME!
 420:    *
 421:    * @see #getEditor
 422:    */
 423:   public void setEditor(JComponent editor)
 424:   {
 425:     if (editor == null)
 426:       throw new IllegalArgumentException("editor may not be null");
 427: 
 428:     if (this.editor instanceof DefaultEditor)
 429:       ((DefaultEditor) editor).dismiss(this);
 430:     else if (this.editor instanceof ChangeListener)
 431:       removeChangeListener((ChangeListener) this.editor);
 432: 
 433:     if (editor instanceof ChangeListener)
 434:       addChangeListener((ChangeListener) editor);
 435: 
 436:     this.editor = editor;
 437:   }
 438: 
 439:   /**
 440:    * Gets the underly model.
 441:    *
 442:    * @return the underly model
 443:    */
 444:   public SpinnerModel getModel()
 445:   {
 446:     return model;
 447:   }
 448: 
 449:   /**
 450:    * Sets a new underlying model.
 451:    *
 452:    * @param newModel the new model to set
 453:    *
 454:    * @exception IllegalArgumentException if newModel is <code>null</code>
 455:    */
 456:   public void setModel(SpinnerModel newModel)
 457:   {
 458:     if (newModel == null)
 459:       throw new IllegalArgumentException();
 460:     
 461:     if (model == newModel)
 462:       return;
 463: 
 464:     SpinnerModel oldModel = model;
 465:     model = newModel;
 466:     firePropertyChange("model", oldModel, newModel);
 467: 
 468:     if (editor == null)
 469:       setEditor(createEditor(model));
 470:   }
 471: 
 472:   /**
 473:    * Gets the next value without changing the current value.
 474:    *
 475:    * @return the next value
 476:    *
 477:    * @see javax.swing.SpinnerModel#getNextValue
 478:    */
 479:   public Object getNextValue()
 480:   {
 481:     return model.getNextValue();
 482:   }
 483: 
 484:   /**
 485:    * Gets the previous value without changing the current value.
 486:    *
 487:    * @return the previous value
 488:    *
 489:    * @see javax.swing.SpinnerModel#getPreviousValue
 490:    */
 491:   public Object getPreviousValue()
 492:   {
 493:     return model.getPreviousValue();
 494:   }
 495: 
 496:   /**
 497:    * Gets the <code>SpinnerUI</code> that handles this spinner
 498:    *
 499:    * @return the <code>SpinnerUI</code>
 500:    */
 501:   public SpinnerUI getUI()
 502:   {
 503:     return (SpinnerUI) ui;
 504:   }
 505: 
 506:   /**
 507:    * Gets the current value of the spinner, according to the underly model,
 508:    * not the UI.
 509:    *
 510:    * @return the current value
 511:    *
 512:    * @see javax.swing.SpinnerModel#getValue
 513:    */
 514:   public Object getValue()
 515:   {
 516:     return model.getValue();
 517:   }
 518: 
 519:   /**
 520:    * DOCUMENT ME!
 521:    *
 522:    * @param value DOCUMENT ME!
 523:    */
 524:   public void setValue(Object value)
 525:   {
 526:     model.setValue(value);
 527:   }
 528: 
 529:   /**
 530:    * This method returns a name to identify which look and feel class will be
 531:    * the UI delegate for this spinner.
 532:    *
 533:    * @return The UIClass identifier. "SpinnerUI"
 534:    */
 535:   public String getUIClassID()
 536:   {
 537:     return "SpinnerUI";
 538:   }
 539: 
 540:   /**
 541:    * This method resets the spinner's UI delegate to the default UI for the
 542:    * current look and feel.
 543:    */
 544:   public void updateUI()
 545:   {
 546:     setUI((SpinnerUI) UIManager.getUI(this));
 547:   }
 548: 
 549:   /**
 550:    * This method sets the spinner's UI delegate.
 551:    *
 552:    * @param ui The spinner's UI delegate.
 553:    */
 554:   public void setUI(SpinnerUI ui)
 555:   {
 556:     super.setUI(ui);
 557:   }
 558: 
 559:   /**
 560:    * Adds a <code>ChangeListener</code>
 561:    *
 562:    * @param listener the listener to add
 563:    */
 564:   public void addChangeListener(ChangeListener listener)
 565:   {
 566:     listenerList.add(ChangeListener.class, listener);
 567:   }
 568: 
 569:   /**
 570:    * Remove a particular listener
 571:    *
 572:    * @param listener the listener to remove
 573:    */
 574:   public void removeChangeListener(ChangeListener listener)
 575:   {
 576:     listenerList.remove(ChangeListener.class, listener);
 577:   }
 578: 
 579:   /**
 580:    * Gets all the <code>ChangeListener</code>s
 581:    *
 582:    * @return all the <code>ChangeListener</code>s
 583:    */
 584:   public ChangeListener[] getChangeListeners()
 585:   {
 586:     return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
 587:   }
 588: 
 589:   /**
 590:    * Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s
 591:    * added to this <code>JSpinner</code>
 592:    */
 593:   protected void fireStateChanged()
 594:   {
 595:     ChangeEvent evt = new ChangeEvent(this);
 596:     ChangeListener[] listeners = getChangeListeners();
 597: 
 598:     for (int i = 0; i < listeners.length; ++i)
 599:       listeners[i].stateChanged(evt);
 600:   }
 601: 
 602:   /**
 603:    * Creates an editor for this <code>JSpinner</code>. Really, it should be a
 604:    * <code>JSpinner.DefaultEditor</code>, but since that should be
 605:    * implemented by a JFormattedTextField, and one is not written, I am just
 606:    * using a dummy one backed by a JLabel.
 607:    *
 608:    * @param model DOCUMENT ME!
 609:    *
 610:    * @return the default editor
 611:    */
 612:   protected JComponent createEditor(SpinnerModel model)
 613:   {
 614:     if (model instanceof SpinnerDateModel)
 615:       return new DateEditor(this);
 616:     else if (model instanceof SpinnerNumberModel)
 617:       return new NumberEditor(this);
 618:     else
 619:       return new DefaultEditor(this);
 620:   }
 621: }