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