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() throws ParseException
 112:     {
 113:       // TODO: Implement this properly.
 114:     }
 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: Implement this properly.
 188:     }
 189:     
 190:     /**
 191:      * DOCUMENT ME!
 192:      *
 193:      * @param event DOCUMENT ME!
 194:      */
 195:     public void stateChanged(ChangeEvent event)
 196:     {
 197:       // TODO: Implement this properly.
 198:     }
 199:     
 200:     public void removeLayoutComponent(Component child)
 201:     {
 202:       // Nothing to do here.
 203:     }
 204: 
 205:     /**
 206:      * DOCUMENT ME!
 207:      *
 208:      * @param name DOCUMENT ME!
 209:      * @param child DOCUMENT ME!
 210:      */
 211:     public void addLayoutComponent(String name, Component child)
 212:     {
 213:       // Nothing to do here.
 214:     }
 215:   }
 216: 
 217:   /**
 218:    * DOCUMENT ME!
 219:    */
 220:   public static class NumberEditor extends DefaultEditor
 221:   {
 222:     /**
 223:      * For compatability with Sun's JDK
 224:      */
 225:     private static final long serialVersionUID = 3791956183098282942L;
 226: 
 227:     /**
 228:      * Creates a new NumberEditor object.
 229:      *
 230:      * @param spinner DOCUMENT ME!
 231:      */
 232:     public NumberEditor(JSpinner spinner)
 233:     {
 234:       super(spinner);
 235:     }
 236: 
 237:     /**
 238:      * Creates a new NumberEditor object.
 239:      *
 240:      * @param spinner DOCUMENT ME!
 241:      */
 242:     public NumberEditor(JSpinner spinner, String decimalFormatPattern)
 243:     {
 244:       super(spinner);
 245:     }
 246: 
 247:     /**
 248:      * DOCUMENT ME!
 249:      *
 250:      * @return DOCUMENT ME!
 251:      */
 252:     public DecimalFormat getFormat()
 253:     {
 254:       return null;
 255:     }
 256: 
 257:     public SpinnerNumberModel getModel()
 258:     {
 259:       return (SpinnerNumberModel) getSpinner().getModel();
 260:     }
 261:   }
 262: 
 263:   /**
 264:    * A <code>JSpinner</code> editor used for the {@link SpinnerListModel}.
 265:    * This editor uses a <code>JFormattedTextField</code> to edit the values
 266:    * of the spinner.
 267:    *
 268:    * @author Roman Kennke (kennke@aicas.com)
 269:    */
 270:   public static class ListEditor extends DefaultEditor
 271:   {
 272:     /**
 273:      * Creates a new instance of <code>ListEditor</code>.
 274:      *
 275:      * @param spinner the spinner for which this editor is used
 276:      */
 277:     public ListEditor(JSpinner spinner)
 278:     {
 279:       super(spinner);
 280:     }
 281: 
 282:     public SpinnerListModel getModel()
 283:     {
 284:       return (SpinnerListModel) getSpinner().getModel();
 285:     }
 286:   }
 287: 
 288:   /**
 289:    * An editor class for a <code>JSpinner</code> that is used
 290:    * for displaying and editing dates (e.g. that uses
 291:    * <code>SpinnerDateModel</code> as model).
 292:    *
 293:    * The editor uses a {@link JTextField} with the value
 294:    * displayed by a {@link DateFormatter} instance.
 295:    */
 296:   public static class DateEditor extends DefaultEditor
 297:   {
 298: 
 299:     /** The serialVersionUID. */
 300:     private static final long serialVersionUID = -4279356973770397815L;
 301: 
 302:     /** The DateFormat instance used to format the date. */
 303:     SimpleDateFormat dateFormat;
 304: 
 305:     /**
 306:      * Creates a new instance of DateEditor for the specified
 307:      * <code>JSpinner</code>.
 308:      *
 309:      * @param spinner the <code>JSpinner</code> for which to
 310:      *     create a <code>DateEditor</code> instance
 311:      */
 312:     public DateEditor(JSpinner spinner)
 313:     {
 314:       super(spinner);
 315:       init(new SimpleDateFormat());
 316:     }
 317: 
 318:     /**
 319:      * Creates a new instance of DateEditor for the specified
 320:      * <code>JSpinner</code> using the specified date format
 321:      * pattern.
 322:      *
 323:      * @param spinner the <code>JSpinner</code> for which to
 324:      *     create a <code>DateEditor</code> instance
 325:      * @param dateFormatPattern the date format to use
 326:      *
 327:      * @see SimpleDateFormat#SimpleDateFormat(String)
 328:      */
 329:     public DateEditor(JSpinner spinner, String dateFormatPattern)
 330:     {
 331:       super(spinner);
 332:       init(new SimpleDateFormat(dateFormatPattern));
 333:     }
 334: 
 335:     /**
 336:      * Initializes the JFormattedTextField for this editor.
 337:      *
 338:      * @param format the date format to use in the formatted text field
 339:      */
 340:     private void init(SimpleDateFormat format)
 341:     {
 342:       dateFormat = format;
 343:       getTextField().setFormatterFactory(
 344:         new JFormattedTextField.AbstractFormatterFactory()
 345:         {
 346:           public JFormattedTextField.AbstractFormatter
 347:           getFormatter(JFormattedTextField ftf)
 348:           {
 349:             return new DateFormatter(dateFormat);
 350:           }
 351:         });
 352:     }
 353: 
 354:     /**
 355:      * Returns the <code>SimpleDateFormat</code> instance that is used to
 356:      * format the date value.
 357:      *
 358:      * @return the <code>SimpleDateFormat</code> instance that is used to
 359:      *     format the date value
 360:      */
 361:     public SimpleDateFormat getFormat()
 362:     {
 363:       return dateFormat;
 364:     }
 365: 
 366:     /**
 367:      * Returns the {@link SpinnerDateModel} that is edited by this editor.
 368:      *
 369:      * @return the <code>SpinnerDateModel</code> that is edited by this editor
 370:      */
 371:     public SpinnerDateModel getModel()
 372:     {
 373:       return (SpinnerDateModel) getSpinner().getModel();
 374:     }
 375:   }
 376: 
 377:   private static final long serialVersionUID = 3412663575706551720L;
 378: 
 379:   /** DOCUMENT ME! */
 380:   private SpinnerModel model;
 381: 
 382:   /** DOCUMENT ME! */
 383:   private JComponent editor;
 384: 
 385:   /** DOCUMENT ME! */
 386:   private ChangeListener listener = new ChangeListener()
 387:     {
 388:       public void stateChanged(ChangeEvent evt)
 389:       {
 390:     fireStateChanged();
 391:       }
 392:     };
 393: 
 394:   /**
 395:    * Creates a JSpinner with <code>SpinnerNumberModel</code>
 396:    *
 397:    * @see javax.swing.SpinnerNumberModel
 398:    */
 399:   public JSpinner()
 400:   {
 401:     this(new SpinnerNumberModel());
 402:   }
 403: 
 404:   /**
 405:    * Creates a JSpinner with the specific model and sets the default editor
 406:    *
 407:    * @param model DOCUMENT ME!
 408:    */
 409:   public JSpinner(SpinnerModel model)
 410:   {
 411:     this.model = model;
 412:     model.addChangeListener(listener);
 413:     setEditor(createEditor(model));
 414:     updateUI();
 415:   }
 416: 
 417:   /**
 418:    * If the editor is <code>JSpinner.DefaultEditor</code>, then forwards the
 419:    * call to it, otherwise do nothing.
 420:    *
 421:    * @throws ParseException DOCUMENT ME!
 422:    */
 423:   public void commitEdit() throws ParseException
 424:   {
 425:     if (editor instanceof DefaultEditor)
 426:       ((DefaultEditor) editor).commitEdit();
 427:   }
 428: 
 429:   /**
 430:    * Gets the current editor
 431:    *
 432:    * @return the current editor
 433:    *
 434:    * @see #setEditor
 435:    */
 436:   public JComponent getEditor()
 437:   {
 438:     return editor;
 439:   }
 440: 
 441:   /**
 442:    * Changes the current editor to the new editor. This methods should remove
 443:    * the old listeners (if any) and adds the new listeners (if any).
 444:    *
 445:    * @param editor the new editor
 446:    *
 447:    * @throws IllegalArgumentException DOCUMENT ME!
 448:    *
 449:    * @see #getEditor
 450:    */
 451:   public void setEditor(JComponent editor)
 452:   {
 453:     if (editor == null)
 454:       throw new IllegalArgumentException("editor may not be null");
 455: 
 456:     if (this.editor instanceof DefaultEditor)
 457:       ((DefaultEditor) editor).dismiss(this);
 458:     else if (this.editor instanceof ChangeListener)
 459:       removeChangeListener((ChangeListener) this.editor);
 460: 
 461:     if (editor instanceof ChangeListener)
 462:       addChangeListener((ChangeListener) editor);
 463: 
 464:     this.editor = editor;
 465:   }
 466: 
 467:   /**
 468:    * Gets the underly model.
 469:    *
 470:    * @return the underly model
 471:    */
 472:   public SpinnerModel getModel()
 473:   {
 474:     return model;
 475:   }
 476: 
 477:   /**
 478:    * Sets a new underlying model.
 479:    *
 480:    * @param newModel the new model to set
 481:    *
 482:    * @exception IllegalArgumentException if newModel is <code>null</code>
 483:    */
 484:   public void setModel(SpinnerModel newModel)
 485:   {
 486:     if (newModel == null)
 487:       throw new IllegalArgumentException();
 488:     
 489:     if (model == newModel)
 490:       return;
 491: 
 492:     SpinnerModel oldModel = model;
 493:     model = newModel;
 494:     firePropertyChange("model", oldModel, newModel);
 495: 
 496:     if (editor == null)
 497:       setEditor(createEditor(model));
 498:   }
 499: 
 500:   /**
 501:    * Gets the next value without changing the current value.
 502:    *
 503:    * @return the next value
 504:    *
 505:    * @see javax.swing.SpinnerModel#getNextValue
 506:    */
 507:   public Object getNextValue()
 508:   {
 509:     return model.getNextValue();
 510:   }
 511: 
 512:   /**
 513:    * Gets the previous value without changing the current value.
 514:    *
 515:    * @return the previous value
 516:    *
 517:    * @see javax.swing.SpinnerModel#getPreviousValue
 518:    */
 519:   public Object getPreviousValue()
 520:   {
 521:     return model.getPreviousValue();
 522:   }
 523: 
 524:   /**
 525:    * Gets the <code>SpinnerUI</code> that handles this spinner
 526:    *
 527:    * @return the <code>SpinnerUI</code>
 528:    */
 529:   public SpinnerUI getUI()
 530:   {
 531:     return (SpinnerUI) ui;
 532:   }
 533: 
 534:   /**
 535:    * Gets the current value of the spinner, according to the underly model,
 536:    * not the UI.
 537:    *
 538:    * @return the current value
 539:    *
 540:    * @see javax.swing.SpinnerModel#getValue
 541:    */
 542:   public Object getValue()
 543:   {
 544:     return model.getValue();
 545:   }
 546: 
 547:   /**
 548:    * DOCUMENT ME!
 549:    *
 550:    * @param value DOCUMENT ME!
 551:    */
 552:   public void setValue(Object value)
 553:   {
 554:     model.setValue(value);
 555:   }
 556: 
 557:   /**
 558:    * This method returns a name to identify which look and feel class will be
 559:    * the UI delegate for this spinner.
 560:    *
 561:    * @return The UIClass identifier. "SpinnerUI"
 562:    */
 563:   public String getUIClassID()
 564:   {
 565:     return "SpinnerUI";
 566:   }
 567: 
 568:   /**
 569:    * This method resets the spinner's UI delegate to the default UI for the
 570:    * current look and feel.
 571:    */
 572:   public void updateUI()
 573:   {
 574:     setUI((SpinnerUI) UIManager.getUI(this));
 575:   }
 576: 
 577:   /**
 578:    * This method sets the spinner's UI delegate.
 579:    *
 580:    * @param ui The spinner's UI delegate.
 581:    */
 582:   public void setUI(SpinnerUI ui)
 583:   {
 584:     super.setUI(ui);
 585:   }
 586: 
 587:   /**
 588:    * Adds a <code>ChangeListener</code>
 589:    *
 590:    * @param listener the listener to add
 591:    */
 592:   public void addChangeListener(ChangeListener listener)
 593:   {
 594:     listenerList.add(ChangeListener.class, listener);
 595:   }
 596: 
 597:   /**
 598:    * Remove a particular listener
 599:    *
 600:    * @param listener the listener to remove
 601:    */
 602:   public void removeChangeListener(ChangeListener listener)
 603:   {
 604:     listenerList.remove(ChangeListener.class, listener);
 605:   }
 606: 
 607:   /**
 608:    * Gets all the <code>ChangeListener</code>s
 609:    *
 610:    * @return all the <code>ChangeListener</code>s
 611:    */
 612:   public ChangeListener[] getChangeListeners()
 613:   {
 614:     return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
 615:   }
 616: 
 617:   /**
 618:    * Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s
 619:    * added to this <code>JSpinner</code>
 620:    */
 621:   protected void fireStateChanged()
 622:   {
 623:     ChangeEvent evt = new ChangeEvent(this);
 624:     ChangeListener[] listeners = getChangeListeners();
 625: 
 626:     for (int i = 0; i < listeners.length; ++i)
 627:       listeners[i].stateChanged(evt);
 628:   }
 629: 
 630:   /**
 631:    * Creates an editor for this <code>JSpinner</code>. Really, it should be a
 632:    * <code>JSpinner.DefaultEditor</code>, but since that should be
 633:    * implemented by a JFormattedTextField, and one is not written, I am just
 634:    * using a dummy one backed by a JLabel.
 635:    *
 636:    * @param model DOCUMENT ME!
 637:    *
 638:    * @return the default editor
 639:    */
 640:   protected JComponent createEditor(SpinnerModel model)
 641:   {
 642:     if (model instanceof SpinnerDateModel)
 643:       return new DateEditor(this);
 644:     else if (model instanceof SpinnerNumberModel)
 645:       return new NumberEditor(this);
 646:     else
 647:       return new DefaultEditor(this);
 648:   }
 649: }