Source for javax.swing.JSpinner

   1: /* JSpinner.java --
   2:    Copyright (C) 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: 
  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.DateFormat;
  49: import java.text.DecimalFormat;
  50: import java.text.NumberFormat;
  51: import java.text.ParseException;
  52: import java.text.SimpleDateFormat;
  53: 
  54: import javax.swing.event.ChangeEvent;
  55: import javax.swing.event.ChangeListener;
  56: import javax.swing.plaf.SpinnerUI;
  57: import javax.swing.text.DateFormatter;
  58: import javax.swing.text.DefaultFormatterFactory;
  59: import javax.swing.text.NumberFormatter;
  60: 
  61: /**
  62:  * A <code>JSpinner</code> is a component that displays a single value from
  63:  * a sequence of values, and provides a convenient means for selecting the
  64:  * previous and next values in the sequence.  Typically the spinner displays
  65:  * a numeric value, but it is possible to display dates or arbitrary items
  66:  * from a list.
  67:  *
  68:  * @author Ka-Hing Cheung
  69:  * 
  70:  * @since 1.4
  71:  */
  72: public class JSpinner extends JComponent
  73: {
  74:   /**
  75:    * The base class for the editor used by the {@link JSpinner} component.  
  76:    * The editor is in fact a panel containing a {@link JFormattedTextField}
  77:    * component.
  78:    */
  79:   public static class DefaultEditor 
  80:     extends JPanel 
  81:     implements ChangeListener, PropertyChangeListener, LayoutManager
  82:   {
  83:     /** The spinner that the editor is allocated to. */
  84:     private JSpinner spinner;
  85: 
  86:     /** The JFormattedTextField that backs the editor. */
  87:     JFormattedTextField ftf;
  88: 
  89:     /**
  90:      * For compatability with Sun's JDK 1.4.2 rev. 5
  91:      */
  92:     private static final long serialVersionUID = -5317788736173368172L;
  93: 
  94:     /**
  95:      * Creates a new <code>DefaultEditor</code> object.  The editor is 
  96:      * registered with the spinner as a {@link ChangeListener} here.
  97:      *
  98:      * @param spinner the <code>JSpinner</code> associated with this editor
  99:      */
 100:     public DefaultEditor(JSpinner spinner)
 101:     {
 102:       super();
 103:       setLayout(this);
 104:       this.spinner = spinner;
 105:       ftf = new JFormattedTextField();
 106:       add(ftf);
 107:       ftf.setValue(spinner.getValue());
 108:       ftf.addPropertyChangeListener(this);
 109:       if (getComponentOrientation().isLeftToRight())
 110:     ftf.setHorizontalAlignment(JTextField.RIGHT);
 111:       else
 112:     ftf.setHorizontalAlignment(JTextField.LEFT);
 113:       spinner.addChangeListener(this);
 114:     }
 115: 
 116:     /**
 117:      * Returns the <code>JSpinner</code> component that the editor is assigned
 118:      * to.
 119:      * 
 120:      * @return The spinner that the editor is assigned to.
 121:      */
 122:     public JSpinner getSpinner()
 123:     {
 124:       return spinner;
 125:     }
 126:     
 127:     /**
 128:      * DOCUMENT ME!
 129:      */
 130:     public void commitEdit() throws ParseException
 131:     {
 132:       // TODO: Implement this properly.
 133:     }
 134: 
 135:     /**
 136:      * Removes the editor from the {@link ChangeListener} list maintained by
 137:      * the specified <code>spinner</code>.
 138:      *
 139:      * @param spinner  the spinner (<code>null</code> not permitted).
 140:      */
 141:     public void dismiss(JSpinner spinner)
 142:     {
 143:       spinner.removeChangeListener(this);
 144:     }
 145: 
 146:     /**
 147:      * Returns the text field used to display and edit the current value in 
 148:      * the spinner.
 149:      *
 150:      * @return The text field.
 151:      */
 152:     public JFormattedTextField getTextField()
 153:     {
 154:       return ftf;
 155:     }
 156:     
 157:     /**
 158:      * Sets the bounds for the child components in this container.  In this
 159:      * case, the text field is the only component to be laid out.
 160:      *
 161:      * @param parent the parent container.
 162:      */
 163:     public void layoutContainer(Container parent)
 164:     {
 165:       Insets insets = getInsets();
 166:       Dimension size = getSize();
 167:       ftf.setBounds(insets.left, insets.top,
 168:                     size.width - insets.left - insets.right,
 169:                     size.height - insets.top - insets.bottom);
 170:     }
 171:     
 172:     /**
 173:      * Calculates the minimum size for this component.  In this case, the
 174:      * text field is the only subcomponent, so the return value is the minimum
 175:      * size of the text field plus the insets of this component.
 176:      *
 177:      * @param parent  the parent container.
 178:      *
 179:      * @return The minimum size.
 180:      */
 181:     public Dimension minimumLayoutSize(Container parent)
 182:     {
 183:       Insets insets = getInsets();
 184:       Dimension minSize = ftf.getMinimumSize();
 185:       return new Dimension(minSize.width + insets.left + insets.right,
 186:                             minSize.height + insets.top + insets.bottom);
 187:     }
 188:     
 189:     /**
 190:      * Calculates the preferred size for this component.  In this case, the
 191:      * text field is the only subcomponent, so the return value is the 
 192:      * preferred size of the text field plus the insets of this component.
 193:      *
 194:      * @param parent  the parent container.
 195:      *
 196:      * @return The preferred size.
 197:      */
 198:     public Dimension preferredLayoutSize(Container parent)
 199:     {
 200:       Insets insets = getInsets();
 201:       Dimension prefSize = ftf.getPreferredSize();
 202:       return new Dimension(prefSize.width + insets.left + insets.right,
 203:                             prefSize.height + insets.top + insets.bottom);
 204:     }
 205:     
 206:     /**
 207:      * Receives notification of property changes.  If the text field's 'value' 
 208:      * property changes, the spinner's model is updated accordingly.
 209:      *
 210:      * @param event the event.
 211:      */
 212:     public void propertyChange(PropertyChangeEvent event)
 213:     {
 214:       if (event.getSource() == ftf) 
 215:         {
 216:           if (event.getPropertyName().equals("value"))
 217:             spinner.getModel().setValue(event.getNewValue());
 218:         }
 219:     }
 220:     
 221:     /**
 222:      * Receives notification of changes in the state of the {@link JSpinner}
 223:      * that the editor belongs to - the content of the text field is updated
 224:      * accordingly.  
 225:      *
 226:      * @param event  the change event.
 227:      */
 228:     public void stateChanged(ChangeEvent event)
 229:     {
 230:       ftf.setValue(spinner.getValue());
 231:     }
 232:     
 233:     /**
 234:      * This method does nothing.  It is required by the {@link LayoutManager}
 235:      * interface, but since this component has a single child, there is no
 236:      * need to use this method.
 237:      * 
 238:      * @param child  the child component to remove.
 239:      */
 240:     public void removeLayoutComponent(Component child)
 241:     {
 242:       // Nothing to do here.
 243:     }
 244: 
 245:     /**
 246:      * This method does nothing.  It is required by the {@link LayoutManager}
 247:      * interface, but since this component has a single child, there is no
 248:      * need to use this method.
 249:      * 
 250:      * @param name  the name.
 251:      * @param child  the child component to add.
 252:      */
 253:     public void addLayoutComponent(String name, Component child)
 254:     {
 255:       // Nothing to do here.
 256:     }
 257:   }
 258: 
 259:   /**
 260:    * A panel containing a {@link JFormattedTextField} that is configured for
 261:    * displaying and editing numbers.  The panel is used as a subcomponent of
 262:    * a {@link JSpinner}.
 263:    * 
 264:    * @see JSpinner#createEditor(SpinnerModel)
 265:    */
 266:   public static class NumberEditor extends DefaultEditor
 267:   {
 268:     /**
 269:      * For compatability with Sun's JDK
 270:      */
 271:     private static final long serialVersionUID = 3791956183098282942L;
 272: 
 273:     /**
 274:      * Creates a new <code>NumberEditor</code> object for the specified 
 275:      * <code>spinner</code>.  The editor is registered with the spinner as a 
 276:      * {@link ChangeListener}.
 277:      *
 278:      * @param spinner the component the editor will be used with.
 279:      */
 280:     public NumberEditor(JSpinner spinner)
 281:     {
 282:       super(spinner);
 283:       NumberEditorFormatter nef = new NumberEditorFormatter();
 284:       nef.setMinimum(getModel().getMinimum());
 285:       nef.setMaximum(getModel().getMaximum());
 286:       ftf.setFormatterFactory(new DefaultFormatterFactory(nef));
 287:     }
 288: 
 289:     /**
 290:      * Creates a new <code>NumberEditor</code> object.
 291:      *
 292:      * @param spinner  the spinner.
 293:      * @param decimalFormatPattern  the number format pattern.
 294:      */
 295:     public NumberEditor(JSpinner spinner, String decimalFormatPattern)
 296:     {
 297:       super(spinner);
 298:       NumberEditorFormatter nef 
 299:           = new NumberEditorFormatter(decimalFormatPattern);
 300:       nef.setMinimum(getModel().getMinimum());
 301:       nef.setMaximum(getModel().getMaximum());
 302:       ftf.setFormatterFactory(new DefaultFormatterFactory(nef));
 303:     }
 304: 
 305:     /**
 306:      * Returns the format used by the text field.
 307:      *
 308:      * @return The format used by the text field.
 309:      */
 310:     public DecimalFormat getFormat()
 311:     {
 312:       NumberFormatter formatter = (NumberFormatter) ftf.getFormatter();
 313:       return (DecimalFormat) formatter.getFormat();
 314:     }
 315: 
 316:     /**
 317:      * Returns the model used by the editor's {@link JSpinner} component,
 318:      * cast to a {@link SpinnerNumberModel}.
 319:      * 
 320:      * @return The model.
 321:      */
 322:     public SpinnerNumberModel getModel()
 323:     {
 324:       return (SpinnerNumberModel) getSpinner().getModel();
 325:     }
 326:   }
 327:   
 328:   static class NumberEditorFormatter 
 329:     extends NumberFormatter
 330:   {
 331:     public NumberEditorFormatter() 
 332:     {
 333:       super(NumberFormat.getInstance());
 334:     }
 335:     public NumberEditorFormatter(String decimalFormatPattern)
 336:     {
 337:       super(new DecimalFormat(decimalFormatPattern));
 338:     }
 339:   }
 340: 
 341:   /**
 342:    * A <code>JSpinner</code> editor used for the {@link SpinnerListModel}.
 343:    * This editor uses a <code>JFormattedTextField</code> to edit the values
 344:    * of the spinner.
 345:    *
 346:    * @author Roman Kennke (kennke@aicas.com)
 347:    */
 348:   public static class ListEditor extends DefaultEditor
 349:   {
 350:     /**
 351:      * Creates a new instance of <code>ListEditor</code>.
 352:      *
 353:      * @param spinner the spinner for which this editor is used
 354:      */
 355:     public ListEditor(JSpinner spinner)
 356:     {
 357:       super(spinner);
 358:     }
 359: 
 360:     /**
 361:      * Returns the spinner's model cast as a {@link SpinnerListModel}.
 362:      * 
 363:      * @return The spinner's model.
 364:      */
 365:     public SpinnerListModel getModel()
 366:     {
 367:       return (SpinnerListModel) getSpinner().getModel();
 368:     }
 369:   }
 370: 
 371:   /**
 372:    * An editor class for a <code>JSpinner</code> that is used
 373:    * for displaying and editing dates (e.g. that uses
 374:    * <code>SpinnerDateModel</code> as model).
 375:    *
 376:    * The editor uses a {@link JTextField} with the value
 377:    * displayed by a {@link DateFormatter} instance.
 378:    */
 379:   public static class DateEditor extends DefaultEditor
 380:   {
 381: 
 382:     /** The serialVersionUID. */
 383:     private static final long serialVersionUID = -4279356973770397815L;
 384: 
 385:     /**
 386:      * Creates a new instance of DateEditor for the specified
 387:      * <code>JSpinner</code>.
 388:      *
 389:      * @param spinner the <code>JSpinner</code> for which to
 390:      *     create a <code>DateEditor</code> instance
 391:      */
 392:     public DateEditor(JSpinner spinner)
 393:     {
 394:       super(spinner);
 395:       DateEditorFormatter nef = new DateEditorFormatter();
 396:       nef.setMinimum(getModel().getStart());
 397:       nef.setMaximum(getModel().getEnd());
 398:       ftf.setFormatterFactory(new DefaultFormatterFactory(nef));
 399:     }
 400: 
 401:     /**
 402:      * Creates a new instance of DateEditor for the specified
 403:      * <code>JSpinner</code> using the specified date format
 404:      * pattern.
 405:      *
 406:      * @param spinner the <code>JSpinner</code> for which to
 407:      *     create a <code>DateEditor</code> instance
 408:      * @param dateFormatPattern the date format to use
 409:      *
 410:      * @see SimpleDateFormat#SimpleDateFormat(String)
 411:      */
 412:     public DateEditor(JSpinner spinner, String dateFormatPattern)
 413:     {
 414:       super(spinner);
 415:       DateEditorFormatter nef = new DateEditorFormatter(dateFormatPattern);
 416:       nef.setMinimum(getModel().getStart());
 417:       nef.setMaximum(getModel().getEnd());
 418:       ftf.setFormatterFactory(new DefaultFormatterFactory(nef));
 419:     }
 420: 
 421:     /**
 422:      * Returns the <code>SimpleDateFormat</code> instance that is used to
 423:      * format the date value.
 424:      *
 425:      * @return the <code>SimpleDateFormat</code> instance that is used to
 426:      *     format the date value
 427:      */
 428:     public SimpleDateFormat getFormat()
 429:     {
 430:       DateFormatter formatter = (DateFormatter) ftf.getFormatter();
 431:       return (SimpleDateFormat) formatter.getFormat();
 432:     }
 433: 
 434:     /**
 435:      * Returns the {@link SpinnerDateModel} that is edited by this editor.
 436:      *
 437:      * @return the <code>SpinnerDateModel</code> that is edited by this editor
 438:      */
 439:     public SpinnerDateModel getModel()
 440:     {
 441:       return (SpinnerDateModel) getSpinner().getModel();
 442:     }
 443:   }
 444: 
 445:   static class DateEditorFormatter 
 446:     extends DateFormatter
 447:   {
 448:     public DateEditorFormatter() 
 449:     {
 450:       super(DateFormat.getInstance());
 451:     }
 452:     public DateEditorFormatter(String dateFormatPattern)
 453:     {
 454:       super(new SimpleDateFormat(dateFormatPattern));
 455:     }
 456:   }
 457: 
 458:   /** 
 459:    * A listener that forwards {@link ChangeEvent} notifications from the model
 460:    * to the {@link JSpinner}'s listeners. 
 461:    */
 462:   class ModelListener implements ChangeListener
 463:   {
 464:     /**
 465:      * Creates a new listener.
 466:      */
 467:     public ModelListener()
 468:     {
 469:       // nothing to do here
 470:     }
 471:     
 472:     /**
 473:      * Receives notification from the model that its state has changed.
 474:      * 
 475:      * @param event  the event (ignored).
 476:      */
 477:     public void stateChanged(ChangeEvent event)
 478:     {
 479:       fireStateChanged();
 480:     }
 481:   }
 482: 
 483:   /** 
 484:    * The model that defines the current value and permitted values for the 
 485:    * spinner. 
 486:    */
 487:   private SpinnerModel model;
 488: 
 489:   /** The current editor. */
 490:   private JComponent editor;
 491: 
 492:   private static final long serialVersionUID = 3412663575706551720L;
 493: 
 494:   /**
 495:    * Creates a new <code>JSpinner</code> with default instance of 
 496:    * {@link SpinnerNumberModel} (that is, a model with value 0, step size 1, 
 497:    * and no upper or lower limit).
 498:    *
 499:    * @see javax.swing.SpinnerNumberModel
 500:    */
 501:   public JSpinner()
 502:   {
 503:     this(new SpinnerNumberModel());
 504:   }
 505: 
 506:   /**
 507:    * Creates a new <code>JSpinner with the specified model.  The 
 508:    * {@link #createEditor(SpinnerModel)} method is used to create an editor
 509:    * that is suitable for the model.
 510:    *
 511:    * @param model the model (<code>null</code> not permitted).
 512:    * 
 513:    * @throws NullPointerException if <code>model</code> is <code>null</code>.
 514:    */
 515:   public JSpinner(SpinnerModel model)
 516:   {
 517:     this.model = model;
 518:     this.editor = createEditor(model);
 519:     model.addChangeListener(new ModelListener());
 520:     updateUI();
 521:   }
 522: 
 523:   /**
 524:    * If the editor is <code>JSpinner.DefaultEditor</code>, then forwards the
 525:    * call to it, otherwise do nothing.
 526:    *
 527:    * @throws ParseException DOCUMENT ME!
 528:    */
 529:   public void commitEdit() throws ParseException
 530:   {
 531:     if (editor instanceof DefaultEditor)
 532:       ((DefaultEditor) editor).commitEdit();
 533:   }
 534: 
 535:   /**
 536:    * Gets the current editor
 537:    *
 538:    * @return the current editor
 539:    *
 540:    * @see #setEditor
 541:    */
 542:   public JComponent getEditor()
 543:   {
 544:     return editor;
 545:   }
 546: 
 547:   /**
 548:    * Changes the current editor to the new editor. The old editor is
 549:    * removed from the spinner's {@link ChangeEvent} list.
 550:    *
 551:    * @param editor the new editor (<code>null</code> not permitted.
 552:    *
 553:    * @throws IllegalArgumentException if <code>editor</code> is 
 554:    *                                  <code>null</code>.
 555:    *
 556:    * @see #getEditor
 557:    */
 558:   public void setEditor(JComponent editor)
 559:   {
 560:     if (editor == null)
 561:       throw new IllegalArgumentException("editor may not be null");
 562: 
 563:     JComponent oldEditor = this.editor;
 564:     if (oldEditor instanceof DefaultEditor)
 565:       ((DefaultEditor) oldEditor).dismiss(this);
 566:     else if (oldEditor instanceof ChangeListener)
 567:       removeChangeListener((ChangeListener) oldEditor);
 568:     
 569:     this.editor = editor;
 570:     firePropertyChange("editor", oldEditor, editor);
 571:   }
 572: 
 573:   /**
 574:    * Returns the model used by the {@link JSpinner} component.
 575:    *
 576:    * @return The model.
 577:    * 
 578:    * @see #setModel(SpinnerModel)
 579:    */
 580:   public SpinnerModel getModel()
 581:   {
 582:     return model;
 583:   }
 584: 
 585:   /**
 586:    * Sets a new underlying model.
 587:    *
 588:    * @param newModel the new model to set
 589:    *
 590:    * @exception IllegalArgumentException if newModel is <code>null</code>
 591:    */
 592:   public void setModel(SpinnerModel newModel)
 593:   {
 594:     if (newModel == null)
 595:       throw new IllegalArgumentException();
 596:     
 597:     if (model == newModel)
 598:       return;
 599: 
 600:     SpinnerModel oldModel = model;
 601:     model = newModel;
 602:     firePropertyChange("model", oldModel, newModel);
 603:     setEditor(createEditor(model));
 604:   }
 605: 
 606:   /**
 607:    * Gets the next value without changing the current value.
 608:    *
 609:    * @return the next value
 610:    *
 611:    * @see javax.swing.SpinnerModel#getNextValue
 612:    */
 613:   public Object getNextValue()
 614:   {
 615:     return model.getNextValue();
 616:   }
 617: 
 618:   /**
 619:    * Gets the previous value without changing the current value.
 620:    *
 621:    * @return the previous value
 622:    *
 623:    * @see javax.swing.SpinnerModel#getPreviousValue
 624:    */
 625:   public Object getPreviousValue()
 626:   {
 627:     return model.getPreviousValue();
 628:   }
 629: 
 630:   /**
 631:    * Gets the <code>SpinnerUI</code> that handles this spinner
 632:    *
 633:    * @return the <code>SpinnerUI</code>
 634:    */
 635:   public SpinnerUI getUI()
 636:   {
 637:     return (SpinnerUI) ui;
 638:   }
 639: 
 640:   /**
 641:    * Gets the current value of the spinner, according to the underly model,
 642:    * not the UI.
 643:    *
 644:    * @return the current value
 645:    *
 646:    * @see javax.swing.SpinnerModel#getValue
 647:    */
 648:   public Object getValue()
 649:   {
 650:     return model.getValue();
 651:   }
 652: 
 653:   /**
 654:    * Sets the value in the model.
 655:    *
 656:    * @param value the new value.
 657:    */
 658:   public void setValue(Object value)
 659:   {
 660:     model.setValue(value);
 661:   }
 662: 
 663:   /**
 664:    * Returns the ID that identifies which look and feel class will be
 665:    * the UI delegate for this spinner.
 666:    *
 667:    * @return <code>"SpinnerUI"</code>.
 668:    */
 669:   public String getUIClassID()
 670:   {
 671:     return "SpinnerUI";
 672:   }
 673: 
 674:   /**
 675:    * This method resets the spinner's UI delegate to the default UI for the
 676:    * current look and feel.
 677:    */
 678:   public void updateUI()
 679:   {
 680:     setUI((SpinnerUI) UIManager.getUI(this));
 681:   }
 682: 
 683:   /**
 684:    * Sets the UI delegate for the component.
 685:    *
 686:    * @param ui The spinner's UI delegate.
 687:    */
 688:   public void setUI(SpinnerUI ui)
 689:   {
 690:     super.setUI(ui);
 691:   }
 692: 
 693:   /**
 694:    * Adds a <code>ChangeListener</code>
 695:    *
 696:    * @param listener the listener to add
 697:    */
 698:   public void addChangeListener(ChangeListener listener)
 699:   {
 700:     listenerList.add(ChangeListener.class, listener);
 701:   }
 702: 
 703:   /**
 704:    * Remove a particular listener
 705:    *
 706:    * @param listener the listener to remove
 707:    */
 708:   public void removeChangeListener(ChangeListener listener)
 709:   {
 710:     listenerList.remove(ChangeListener.class, listener);
 711:   }
 712: 
 713:   /**
 714:    * Gets all the <code>ChangeListener</code>s
 715:    *
 716:    * @return all the <code>ChangeListener</code>s
 717:    */
 718:   public ChangeListener[] getChangeListeners()
 719:   {
 720:     return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
 721:   }
 722: 
 723:   /**
 724:    * Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s
 725:    * added to this <code>JSpinner</code>
 726:    */
 727:   protected void fireStateChanged()
 728:   {
 729:     ChangeEvent evt = new ChangeEvent(this);
 730:     ChangeListener[] listeners = getChangeListeners();
 731: 
 732:     for (int i = 0; i < listeners.length; ++i)
 733:       listeners[i].stateChanged(evt);
 734:   }
 735: 
 736:   /**
 737:    * Creates an editor that is appropriate for the specified <code>model</code>.
 738:    *
 739:    * @param model the model.
 740:    *
 741:    * @return The editor.
 742:    */
 743:   protected JComponent createEditor(SpinnerModel model)
 744:   {
 745:     if (model instanceof SpinnerDateModel)
 746:       return new DateEditor(this);
 747:     else if (model instanceof SpinnerNumberModel)
 748:       return new NumberEditor(this);
 749:     else if (model instanceof SpinnerListModel)
 750:       return new ListEditor(this);
 751:     else
 752:       return new DefaultEditor(this);
 753:   }
 754: }