| /* JSpinner.java -- |
| Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing; |
| |
| import java.awt.Component; |
| import java.awt.Container; |
| import java.awt.Dimension; |
| import java.awt.Insets; |
| import java.awt.LayoutManager; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.text.DateFormat; |
| import java.text.DecimalFormat; |
| import java.text.NumberFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import javax.swing.plaf.SpinnerUI; |
| import javax.swing.text.DateFormatter; |
| import javax.swing.text.DefaultFormatterFactory; |
| import javax.swing.text.NumberFormatter; |
| |
| /** |
| * A <code>JSpinner</code> is a component that displays a single value from |
| * a sequence of values, and provides a convenient means for selecting the |
| * previous and next values in the sequence. Typically the spinner displays |
| * a numeric value, but it is possible to display dates or arbitrary items |
| * from a list. |
| * |
| * @author Ka-Hing Cheung |
| * |
| * @since 1.4 |
| */ |
| public class JSpinner extends JComponent |
| { |
| /** |
| * The base class for the editor used by the {@link JSpinner} component. |
| * The editor is in fact a panel containing a {@link JFormattedTextField} |
| * component. |
| */ |
| public static class DefaultEditor |
| extends JPanel |
| implements ChangeListener, PropertyChangeListener, LayoutManager |
| { |
| /** The spinner that the editor is allocated to. */ |
| private JSpinner spinner; |
| |
| /** The JFormattedTextField that backs the editor. */ |
| JFormattedTextField ftf; |
| |
| /** |
| * For compatability with Sun's JDK 1.4.2 rev. 5 |
| */ |
| private static final long serialVersionUID = -5317788736173368172L; |
| |
| /** |
| * Creates a new <code>DefaultEditor</code> object. The editor is |
| * registered with the spinner as a {@link ChangeListener} here. |
| * |
| * @param spinner the <code>JSpinner</code> associated with this editor |
| */ |
| public DefaultEditor(JSpinner spinner) |
| { |
| super(); |
| setLayout(this); |
| this.spinner = spinner; |
| ftf = new JFormattedTextField(); |
| add(ftf); |
| ftf.setValue(spinner.getValue()); |
| ftf.addPropertyChangeListener(this); |
| if (getComponentOrientation().isLeftToRight()) |
| ftf.setHorizontalAlignment(JTextField.RIGHT); |
| else |
| ftf.setHorizontalAlignment(JTextField.LEFT); |
| spinner.addChangeListener(this); |
| } |
| |
| /** |
| * Returns the <code>JSpinner</code> component that the editor is assigned |
| * to. |
| * |
| * @return The spinner that the editor is assigned to. |
| */ |
| public JSpinner getSpinner() |
| { |
| return spinner; |
| } |
| |
| /** |
| * DOCUMENT ME! |
| */ |
| public void commitEdit() throws ParseException |
| { |
| // TODO: Implement this properly. |
| } |
| |
| /** |
| * Removes the editor from the {@link ChangeListener} list maintained by |
| * the specified <code>spinner</code>. |
| * |
| * @param spinner the spinner (<code>null</code> not permitted). |
| */ |
| public void dismiss(JSpinner spinner) |
| { |
| spinner.removeChangeListener(this); |
| } |
| |
| /** |
| * Returns the text field used to display and edit the current value in |
| * the spinner. |
| * |
| * @return The text field. |
| */ |
| public JFormattedTextField getTextField() |
| { |
| return ftf; |
| } |
| |
| /** |
| * Sets the bounds for the child components in this container. In this |
| * case, the text field is the only component to be laid out. |
| * |
| * @param parent the parent container. |
| */ |
| public void layoutContainer(Container parent) |
| { |
| Insets insets = getInsets(); |
| Dimension size = getSize(); |
| ftf.setBounds(insets.left, insets.top, |
| size.width - insets.left - insets.right, |
| size.height - insets.top - insets.bottom); |
| } |
| |
| /** |
| * Calculates the minimum size for this component. In this case, the |
| * text field is the only subcomponent, so the return value is the minimum |
| * size of the text field plus the insets of this component. |
| * |
| * @param parent the parent container. |
| * |
| * @return The minimum size. |
| */ |
| public Dimension minimumLayoutSize(Container parent) |
| { |
| Insets insets = getInsets(); |
| Dimension minSize = ftf.getMinimumSize(); |
| return new Dimension(minSize.width + insets.left + insets.right, |
| minSize.height + insets.top + insets.bottom); |
| } |
| |
| /** |
| * Calculates the preferred size for this component. In this case, the |
| * text field is the only subcomponent, so the return value is the |
| * preferred size of the text field plus the insets of this component. |
| * |
| * @param parent the parent container. |
| * |
| * @return The preferred size. |
| */ |
| public Dimension preferredLayoutSize(Container parent) |
| { |
| Insets insets = getInsets(); |
| Dimension prefSize = ftf.getPreferredSize(); |
| return new Dimension(prefSize.width + insets.left + insets.right, |
| prefSize.height + insets.top + insets.bottom); |
| } |
| |
| /** |
| * Receives notification of property changes. If the text field's 'value' |
| * property changes, the spinner's model is updated accordingly. |
| * |
| * @param event the event. |
| */ |
| public void propertyChange(PropertyChangeEvent event) |
| { |
| if (event.getSource() == ftf) |
| { |
| if (event.getPropertyName().equals("value")) |
| spinner.getModel().setValue(event.getNewValue()); |
| } |
| } |
| |
| /** |
| * Receives notification of changes in the state of the {@link JSpinner} |
| * that the editor belongs to - the content of the text field is updated |
| * accordingly. |
| * |
| * @param event the change event. |
| */ |
| public void stateChanged(ChangeEvent event) |
| { |
| ftf.setValue(spinner.getValue()); |
| } |
| |
| /** |
| * This method does nothing. It is required by the {@link LayoutManager} |
| * interface, but since this component has a single child, there is no |
| * need to use this method. |
| * |
| * @param child the child component to remove. |
| */ |
| public void removeLayoutComponent(Component child) |
| { |
| // Nothing to do here. |
| } |
| |
| /** |
| * This method does nothing. It is required by the {@link LayoutManager} |
| * interface, but since this component has a single child, there is no |
| * need to use this method. |
| * |
| * @param name the name. |
| * @param child the child component to add. |
| */ |
| public void addLayoutComponent(String name, Component child) |
| { |
| // Nothing to do here. |
| } |
| } |
| |
| /** |
| * A panel containing a {@link JFormattedTextField} that is configured for |
| * displaying and editing numbers. The panel is used as a subcomponent of |
| * a {@link JSpinner}. |
| * |
| * @see JSpinner#createEditor(SpinnerModel) |
| */ |
| public static class NumberEditor extends DefaultEditor |
| { |
| /** |
| * For compatability with Sun's JDK |
| */ |
| private static final long serialVersionUID = 3791956183098282942L; |
| |
| /** |
| * Creates a new <code>NumberEditor</code> object for the specified |
| * <code>spinner</code>. The editor is registered with the spinner as a |
| * {@link ChangeListener}. |
| * |
| * @param spinner the component the editor will be used with. |
| */ |
| public NumberEditor(JSpinner spinner) |
| { |
| super(spinner); |
| NumberEditorFormatter nef = new NumberEditorFormatter(); |
| nef.setMinimum(getModel().getMinimum()); |
| nef.setMaximum(getModel().getMaximum()); |
| ftf.setFormatterFactory(new DefaultFormatterFactory(nef)); |
| } |
| |
| /** |
| * Creates a new <code>NumberEditor</code> object. |
| * |
| * @param spinner the spinner. |
| * @param decimalFormatPattern the number format pattern. |
| */ |
| public NumberEditor(JSpinner spinner, String decimalFormatPattern) |
| { |
| super(spinner); |
| NumberEditorFormatter nef |
| = new NumberEditorFormatter(decimalFormatPattern); |
| nef.setMinimum(getModel().getMinimum()); |
| nef.setMaximum(getModel().getMaximum()); |
| ftf.setFormatterFactory(new DefaultFormatterFactory(nef)); |
| } |
| |
| /** |
| * Returns the format used by the text field. |
| * |
| * @return The format used by the text field. |
| */ |
| public DecimalFormat getFormat() |
| { |
| NumberFormatter formatter = (NumberFormatter) ftf.getFormatter(); |
| return (DecimalFormat) formatter.getFormat(); |
| } |
| |
| /** |
| * Returns the model used by the editor's {@link JSpinner} component, |
| * cast to a {@link SpinnerNumberModel}. |
| * |
| * @return The model. |
| */ |
| public SpinnerNumberModel getModel() |
| { |
| return (SpinnerNumberModel) getSpinner().getModel(); |
| } |
| } |
| |
| static class NumberEditorFormatter |
| extends NumberFormatter |
| { |
| public NumberEditorFormatter() |
| { |
| super(NumberFormat.getInstance()); |
| } |
| public NumberEditorFormatter(String decimalFormatPattern) |
| { |
| super(new DecimalFormat(decimalFormatPattern)); |
| } |
| } |
| |
| /** |
| * A <code>JSpinner</code> editor used for the {@link SpinnerListModel}. |
| * This editor uses a <code>JFormattedTextField</code> to edit the values |
| * of the spinner. |
| * |
| * @author Roman Kennke (kennke@aicas.com) |
| */ |
| public static class ListEditor extends DefaultEditor |
| { |
| /** |
| * Creates a new instance of <code>ListEditor</code>. |
| * |
| * @param spinner the spinner for which this editor is used |
| */ |
| public ListEditor(JSpinner spinner) |
| { |
| super(spinner); |
| } |
| |
| /** |
| * Returns the spinner's model cast as a {@link SpinnerListModel}. |
| * |
| * @return The spinner's model. |
| */ |
| public SpinnerListModel getModel() |
| { |
| return (SpinnerListModel) getSpinner().getModel(); |
| } |
| } |
| |
| /** |
| * An editor class for a <code>JSpinner</code> that is used |
| * for displaying and editing dates (e.g. that uses |
| * <code>SpinnerDateModel</code> as model). |
| * |
| * The editor uses a {@link JTextField} with the value |
| * displayed by a {@link DateFormatter} instance. |
| */ |
| public static class DateEditor extends DefaultEditor |
| { |
| |
| /** The serialVersionUID. */ |
| private static final long serialVersionUID = -4279356973770397815L; |
| |
| /** |
| * Creates a new instance of DateEditor for the specified |
| * <code>JSpinner</code>. |
| * |
| * @param spinner the <code>JSpinner</code> for which to |
| * create a <code>DateEditor</code> instance |
| */ |
| public DateEditor(JSpinner spinner) |
| { |
| super(spinner); |
| DateEditorFormatter nef = new DateEditorFormatter(); |
| nef.setMinimum(getModel().getStart()); |
| nef.setMaximum(getModel().getEnd()); |
| ftf.setFormatterFactory(new DefaultFormatterFactory(nef)); |
| } |
| |
| /** |
| * Creates a new instance of DateEditor for the specified |
| * <code>JSpinner</code> using the specified date format |
| * pattern. |
| * |
| * @param spinner the <code>JSpinner</code> for which to |
| * create a <code>DateEditor</code> instance |
| * @param dateFormatPattern the date format to use |
| * |
| * @see SimpleDateFormat#SimpleDateFormat(String) |
| */ |
| public DateEditor(JSpinner spinner, String dateFormatPattern) |
| { |
| super(spinner); |
| DateEditorFormatter nef = new DateEditorFormatter(dateFormatPattern); |
| nef.setMinimum(getModel().getStart()); |
| nef.setMaximum(getModel().getEnd()); |
| ftf.setFormatterFactory(new DefaultFormatterFactory(nef)); |
| } |
| |
| /** |
| * Returns the <code>SimpleDateFormat</code> instance that is used to |
| * format the date value. |
| * |
| * @return the <code>SimpleDateFormat</code> instance that is used to |
| * format the date value |
| */ |
| public SimpleDateFormat getFormat() |
| { |
| DateFormatter formatter = (DateFormatter) ftf.getFormatter(); |
| return (SimpleDateFormat) formatter.getFormat(); |
| } |
| |
| /** |
| * Returns the {@link SpinnerDateModel} that is edited by this editor. |
| * |
| * @return the <code>SpinnerDateModel</code> that is edited by this editor |
| */ |
| public SpinnerDateModel getModel() |
| { |
| return (SpinnerDateModel) getSpinner().getModel(); |
| } |
| } |
| |
| static class DateEditorFormatter |
| extends DateFormatter |
| { |
| public DateEditorFormatter() |
| { |
| super(DateFormat.getInstance()); |
| } |
| public DateEditorFormatter(String dateFormatPattern) |
| { |
| super(new SimpleDateFormat(dateFormatPattern)); |
| } |
| } |
| |
| /** |
| * A listener that forwards {@link ChangeEvent} notifications from the model |
| * to the {@link JSpinner}'s listeners. |
| */ |
| class ModelListener implements ChangeListener |
| { |
| /** |
| * Creates a new listener. |
| */ |
| public ModelListener() |
| { |
| // nothing to do here |
| } |
| |
| /** |
| * Receives notification from the model that its state has changed. |
| * |
| * @param event the event (ignored). |
| */ |
| public void stateChanged(ChangeEvent event) |
| { |
| fireStateChanged(); |
| } |
| } |
| |
| /** |
| * The model that defines the current value and permitted values for the |
| * spinner. |
| */ |
| private SpinnerModel model; |
| |
| /** The current editor. */ |
| private JComponent editor; |
| |
| private static final long serialVersionUID = 3412663575706551720L; |
| |
| /** |
| * Creates a new <code>JSpinner</code> with default instance of |
| * {@link SpinnerNumberModel} (that is, a model with value 0, step size 1, |
| * and no upper or lower limit). |
| * |
| * @see javax.swing.SpinnerNumberModel |
| */ |
| public JSpinner() |
| { |
| this(new SpinnerNumberModel()); |
| } |
| |
| /** |
| * Creates a new <code>JSpinner with the specified model. The |
| * {@link #createEditor(SpinnerModel)} method is used to create an editor |
| * that is suitable for the model. |
| * |
| * @param model the model (<code>null</code> not permitted). |
| * |
| * @throws NullPointerException if <code>model</code> is <code>null</code>. |
| */ |
| public JSpinner(SpinnerModel model) |
| { |
| this.model = model; |
| this.editor = createEditor(model); |
| model.addChangeListener(new ModelListener()); |
| updateUI(); |
| } |
| |
| /** |
| * If the editor is <code>JSpinner.DefaultEditor</code>, then forwards the |
| * call to it, otherwise do nothing. |
| * |
| * @throws ParseException DOCUMENT ME! |
| */ |
| public void commitEdit() throws ParseException |
| { |
| if (editor instanceof DefaultEditor) |
| ((DefaultEditor) editor).commitEdit(); |
| } |
| |
| /** |
| * Gets the current editor |
| * |
| * @return the current editor |
| * |
| * @see #setEditor |
| */ |
| public JComponent getEditor() |
| { |
| return editor; |
| } |
| |
| /** |
| * Changes the current editor to the new editor. The old editor is |
| * removed from the spinner's {@link ChangeEvent} list. |
| * |
| * @param editor the new editor (<code>null</code> not permitted. |
| * |
| * @throws IllegalArgumentException if <code>editor</code> is |
| * <code>null</code>. |
| * |
| * @see #getEditor |
| */ |
| public void setEditor(JComponent editor) |
| { |
| if (editor == null) |
| throw new IllegalArgumentException("editor may not be null"); |
| |
| JComponent oldEditor = this.editor; |
| if (oldEditor instanceof DefaultEditor) |
| ((DefaultEditor) oldEditor).dismiss(this); |
| else if (oldEditor instanceof ChangeListener) |
| removeChangeListener((ChangeListener) oldEditor); |
| |
| this.editor = editor; |
| firePropertyChange("editor", oldEditor, editor); |
| } |
| |
| /** |
| * Returns the model used by the {@link JSpinner} component. |
| * |
| * @return The model. |
| * |
| * @see #setModel(SpinnerModel) |
| */ |
| public SpinnerModel getModel() |
| { |
| return model; |
| } |
| |
| /** |
| * Sets a new underlying model. |
| * |
| * @param newModel the new model to set |
| * |
| * @exception IllegalArgumentException if newModel is <code>null</code> |
| */ |
| public void setModel(SpinnerModel newModel) |
| { |
| if (newModel == null) |
| throw new IllegalArgumentException(); |
| |
| if (model == newModel) |
| return; |
| |
| SpinnerModel oldModel = model; |
| model = newModel; |
| firePropertyChange("model", oldModel, newModel); |
| setEditor(createEditor(model)); |
| } |
| |
| /** |
| * Gets the next value without changing the current value. |
| * |
| * @return the next value |
| * |
| * @see javax.swing.SpinnerModel#getNextValue |
| */ |
| public Object getNextValue() |
| { |
| return model.getNextValue(); |
| } |
| |
| /** |
| * Gets the previous value without changing the current value. |
| * |
| * @return the previous value |
| * |
| * @see javax.swing.SpinnerModel#getPreviousValue |
| */ |
| public Object getPreviousValue() |
| { |
| return model.getPreviousValue(); |
| } |
| |
| /** |
| * Gets the <code>SpinnerUI</code> that handles this spinner |
| * |
| * @return the <code>SpinnerUI</code> |
| */ |
| public SpinnerUI getUI() |
| { |
| return (SpinnerUI) ui; |
| } |
| |
| /** |
| * Gets the current value of the spinner, according to the underly model, |
| * not the UI. |
| * |
| * @return the current value |
| * |
| * @see javax.swing.SpinnerModel#getValue |
| */ |
| public Object getValue() |
| { |
| return model.getValue(); |
| } |
| |
| /** |
| * Sets the value in the model. |
| * |
| * @param value the new value. |
| */ |
| public void setValue(Object value) |
| { |
| model.setValue(value); |
| } |
| |
| /** |
| * Returns the ID that identifies which look and feel class will be |
| * the UI delegate for this spinner. |
| * |
| * @return <code>"SpinnerUI"</code>. |
| */ |
| public String getUIClassID() |
| { |
| return "SpinnerUI"; |
| } |
| |
| /** |
| * This method resets the spinner's UI delegate to the default UI for the |
| * current look and feel. |
| */ |
| public void updateUI() |
| { |
| setUI((SpinnerUI) UIManager.getUI(this)); |
| } |
| |
| /** |
| * Sets the UI delegate for the component. |
| * |
| * @param ui The spinner's UI delegate. |
| */ |
| public void setUI(SpinnerUI ui) |
| { |
| super.setUI(ui); |
| } |
| |
| /** |
| * Adds a <code>ChangeListener</code> |
| * |
| * @param listener the listener to add |
| */ |
| public void addChangeListener(ChangeListener listener) |
| { |
| listenerList.add(ChangeListener.class, listener); |
| } |
| |
| /** |
| * Remove a particular listener |
| * |
| * @param listener the listener to remove |
| */ |
| public void removeChangeListener(ChangeListener listener) |
| { |
| listenerList.remove(ChangeListener.class, listener); |
| } |
| |
| /** |
| * Gets all the <code>ChangeListener</code>s |
| * |
| * @return all the <code>ChangeListener</code>s |
| */ |
| public ChangeListener[] getChangeListeners() |
| { |
| return (ChangeListener[]) listenerList.getListeners(ChangeListener.class); |
| } |
| |
| /** |
| * Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s |
| * added to this <code>JSpinner</code> |
| */ |
| protected void fireStateChanged() |
| { |
| ChangeEvent evt = new ChangeEvent(this); |
| ChangeListener[] listeners = getChangeListeners(); |
| |
| for (int i = 0; i < listeners.length; ++i) |
| listeners[i].stateChanged(evt); |
| } |
| |
| /** |
| * Creates an editor that is appropriate for the specified <code>model</code>. |
| * |
| * @param model the model. |
| * |
| * @return The editor. |
| */ |
| protected JComponent createEditor(SpinnerModel model) |
| { |
| if (model instanceof SpinnerDateModel) |
| return new DateEditor(this); |
| else if (model instanceof SpinnerNumberModel) |
| return new NumberEditor(this); |
| else if (model instanceof SpinnerListModel) |
| return new ListEditor(this); |
| else |
| return new DefaultEditor(this); |
| } |
| } |