View Javadoc
1   /*
2    * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.swing;
27  
28  import java.util.*;
29  import java.io.Serializable;
30  
31  
32  /**
33   * A <code>SpinnerModel</code> for sequences of numbers.
34   * The upper and lower bounds of the sequence are defined
35   * by properties called <code>minimum</code> and
36   * <code>maximum</code>. The size of the increase or decrease
37   * computed by the <code>nextValue</code> and
38   * <code>previousValue</code> methods is defined by a property called
39   * <code>stepSize</code>.  The <code>minimum</code> and
40   * <code>maximum</code> properties can be <code>null</code>
41   * to indicate that the sequence has no lower or upper limit.
42   * All of the properties in this class are defined in terms of two
43   * generic types: <code>Number</code> and
44   * <code>Comparable</code>, so that all Java numeric types
45   * may be accommodated.  Internally, there's only support for
46   * values whose type is one of the primitive <code>Number</code> types:
47   * <code>Double</code>, <code>Float</code>, <code>Long</code>,
48   * <code>Integer</code>, <code>Short</code>, or <code>Byte</code>.
49   * <p>
50   * To create a <code>SpinnerNumberModel</code> for the integer
51   * range zero to one hundred, with
52   * fifty as the initial value, one could write:
53   * <pre>
54   * Integer value = new Integer(50);
55   * Integer min = new Integer(0);
56   * Integer max = new Integer(100);
57   * Integer step = new Integer(1);
58   * SpinnerNumberModel model = new SpinnerNumberModel(value, min, max, step);
59   * int fifty = model.getNumber().intValue();
60   * </pre>
61   * <p>
62   * Spinners for integers and doubles are common, so special constructors
63   * for these cases are provided.  For example to create the model in
64   * the previous example, one could also write:
65   * <pre>
66   * SpinnerNumberModel model = new SpinnerNumberModel(50, 0, 100, 1);
67   * </pre>
68   * <p>
69   * This model inherits a <code>ChangeListener</code>.
70   * The <code>ChangeListeners</code> are notified
71   * whenever the model's <code>value</code>, <code>stepSize</code>,
72   * <code>minimum</code>, or <code>maximum</code> properties changes.
73   *
74   * @see JSpinner
75   * @see SpinnerModel
76   * @see AbstractSpinnerModel
77   * @see SpinnerListModel
78   * @see SpinnerDateModel
79   *
80   * @author Hans Muller
81   * @since 1.4
82  */
83  public class SpinnerNumberModel extends AbstractSpinnerModel implements Serializable
84  {
85      private Number stepSize, value;
86      private Comparable minimum, maximum;
87  
88  
89      /**
90       * Constructs a <code>SpinnerModel</code> that represents
91       * a closed sequence of
92       * numbers from <code>minimum</code> to <code>maximum</code>.  The
93       * <code>nextValue</code> and <code>previousValue</code> methods
94       * compute elements of the sequence by adding or subtracting
95       * <code>stepSize</code> respectively.  All of the parameters
96       * must be mutually <code>Comparable</code>, <code>value</code>
97       * and <code>stepSize</code> must be instances of <code>Integer</code>
98       * <code>Long</code>, <code>Float</code>, or <code>Double</code>.
99       * <p>
100      * The <code>minimum</code> and <code>maximum</code> parameters
101      * can be <code>null</code> to indicate that the range doesn't
102      * have an upper or lower bound.
103      * If <code>value</code> or <code>stepSize</code> is <code>null</code>,
104      * or if both <code>minimum</code> and <code>maximum</code>
105      * are specified and <code>minimum &gt; maximum</code> then an
106      * <code>IllegalArgumentException</code> is thrown.
107      * Similarly if <code>(minimum &lt;= value &lt;= maximum</code>) is false,
108      * an <code>IllegalArgumentException</code> is thrown.
109      *
110      * @param value the current (non <code>null</code>) value of the model
111      * @param minimum the first number in the sequence or <code>null</code>
112      * @param maximum the last number in the sequence or <code>null</code>
113      * @param stepSize the difference between elements of the sequence
114      *
115      * @throws IllegalArgumentException if stepSize or value is
116      *     <code>null</code> or if the following expression is false:
117      *     <code>minimum &lt;= value &lt;= maximum</code>
118      */
119     public SpinnerNumberModel(Number value, Comparable minimum, Comparable maximum, Number stepSize) {
120         if ((value == null) || (stepSize == null)) {
121             throw new IllegalArgumentException("value and stepSize must be non-null");
122         }
123         if (!(((minimum == null) || (minimum.compareTo(value) <= 0)) &&
124               ((maximum == null) || (maximum.compareTo(value) >= 0)))) {
125             throw new IllegalArgumentException("(minimum <= value <= maximum) is false");
126         }
127         this.value = value;
128         this.minimum = minimum;
129         this.maximum = maximum;
130         this.stepSize = stepSize;
131     }
132 
133 
134     /**
135      * Constructs a <code>SpinnerNumberModel</code> with the specified
136      * <code>value</code>, <code>minimum</code>/<code>maximum</code> bounds,
137      * and <code>stepSize</code>.
138      *
139      * @param value the current value of the model
140      * @param minimum the first number in the sequence
141      * @param maximum the last number in the sequence
142      * @param stepSize the difference between elements of the sequence
143      * @throws IllegalArgumentException if the following expression is false:
144      *     <code>minimum &lt;= value &lt;= maximum</code>
145      */
146     public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize) {
147         this(Integer.valueOf(value), Integer.valueOf(minimum), Integer.valueOf(maximum), Integer.valueOf(stepSize));
148     }
149 
150 
151     /**
152      * Constructs a <code>SpinnerNumberModel</code> with the specified
153      * <code>value</code>, <code>minimum</code>/<code>maximum</code> bounds,
154      * and <code>stepSize</code>.
155      *
156      * @param value the current value of the model
157      * @param minimum the first number in the sequence
158      * @param maximum the last number in the sequence
159      * @param stepSize the difference between elements of the sequence
160      * @throws IllegalArgumentException   if the following expression is false:
161      *     <code>minimum &lt;= value &lt;= maximum</code>
162      */
163     public SpinnerNumberModel(double value, double minimum, double maximum, double stepSize) {
164         this(new Double(value), new Double(minimum), new Double(maximum), new Double(stepSize));
165     }
166 
167 
168     /**
169      * Constructs a <code>SpinnerNumberModel</code> with no
170      * <code>minimum</code> or <code>maximum</code> value,
171      * <code>stepSize</code> equal to one, and an initial value of zero.
172      */
173     public SpinnerNumberModel() {
174         this(Integer.valueOf(0), null, null, Integer.valueOf(1));
175     }
176 
177 
178     /**
179      * Changes the lower bound for numbers in this sequence.
180      * If <code>minimum</code> is <code>null</code>,
181      * then there is no lower bound.  No bounds checking is done here;
182      * the new <code>minimum</code> value may invalidate the
183      * <code>(minimum &lt;= value &lt;= maximum)</code>
184      * invariant enforced by the constructors.  This is to simplify updating
185      * the model, naturally one should ensure that the invariant is true
186      * before calling the <code>getNextValue</code>,
187      * <code>getPreviousValue</code>, or <code>setValue</code> methods.
188      * <p>
189      * Typically this property is a <code>Number</code> of the same type
190      * as the <code>value</code> however it's possible to use any
191      * <code>Comparable</code> with a <code>compareTo</code>
192      * method for a <code>Number</code> with the same type as the value.
193      * For example if value was a <code>Long</code>,
194      * <code>minimum</code> might be a Date subclass defined like this:
195      * <pre>
196      * MyDate extends Date {  // Date already implements Comparable
197      *     public int compareTo(Long o) {
198      *         long t = getTime();
199      *         return (t &lt; o.longValue() ? -1 : (t == o.longValue() ? 0 : 1));
200      *     }
201      * }
202      * </pre>
203      * <p>
204      * This method fires a <code>ChangeEvent</code>
205      * if the <code>minimum</code> has changed.
206      *
207      * @param minimum a <code>Comparable</code> that has a
208      *     <code>compareTo</code> method for <code>Number</code>s with
209      *     the same type as <code>value</code>
210      * @see #getMinimum
211      * @see #setMaximum
212      * @see SpinnerModel#addChangeListener
213      */
214     public void setMinimum(Comparable minimum) {
215         if ((minimum == null) ? (this.minimum != null) : !minimum.equals(this.minimum)) {
216             this.minimum = minimum;
217             fireStateChanged();
218         }
219     }
220 
221 
222     /**
223      * Returns the first number in this sequence.
224      *
225      * @return the value of the <code>minimum</code> property
226      * @see #setMinimum
227      */
228     public Comparable getMinimum() {
229         return minimum;
230     }
231 
232 
233     /**
234      * Changes the upper bound for numbers in this sequence.
235      * If <code>maximum</code> is <code>null</code>, then there
236      * is no upper bound.  No bounds checking is done here; the new
237      * <code>maximum</code> value may invalidate the
238      * <code>(minimum &lt;= value &lt; maximum)</code>
239      * invariant enforced by the constructors.  This is to simplify updating
240      * the model, naturally one should ensure that the invariant is true
241      * before calling the <code>next</code>, <code>previous</code>,
242      * or <code>setValue</code> methods.
243      * <p>
244      * Typically this property is a <code>Number</code> of the same type
245      * as the <code>value</code> however it's possible to use any
246      * <code>Comparable</code> with a <code>compareTo</code>
247      * method for a <code>Number</code> with the same type as the value.
248      * See <a href="#setMinimum(java.lang.Comparable)">
249      * <code>setMinimum</code></a> for an example.
250      * <p>
251      * This method fires a <code>ChangeEvent</code> if the
252      * <code>maximum</code> has changed.
253      *
254      * @param maximum a <code>Comparable</code> that has a
255      *     <code>compareTo</code> method for <code>Number</code>s with
256      *     the same type as <code>value</code>
257      * @see #getMaximum
258      * @see #setMinimum
259      * @see SpinnerModel#addChangeListener
260      */
261     public void setMaximum(Comparable maximum) {
262         if ((maximum == null) ? (this.maximum != null) : !maximum.equals(this.maximum)) {
263             this.maximum = maximum;
264             fireStateChanged();
265         }
266     }
267 
268 
269     /**
270      * Returns the last number in the sequence.
271      *
272      * @return the value of the <code>maximum</code> property
273      * @see #setMaximum
274      */
275     public Comparable getMaximum() {
276         return maximum;
277     }
278 
279 
280     /**
281      * Changes the size of the value change computed by the
282      * <code>getNextValue</code> and <code>getPreviousValue</code>
283      * methods.  An <code>IllegalArgumentException</code>
284      * is thrown if <code>stepSize</code> is <code>null</code>.
285      * <p>
286      * This method fires a <code>ChangeEvent</code> if the
287      * <code>stepSize</code> has changed.
288      *
289      * @param stepSize the size of the value change computed by the
290      *     <code>getNextValue</code> and <code>getPreviousValue</code> methods
291      * @see #getNextValue
292      * @see #getPreviousValue
293      * @see #getStepSize
294      * @see SpinnerModel#addChangeListener
295      */
296     public void setStepSize(Number stepSize) {
297         if (stepSize == null) {
298             throw new IllegalArgumentException("null stepSize");
299         }
300         if (!stepSize.equals(this.stepSize)) {
301             this.stepSize = stepSize;
302             fireStateChanged();
303         }
304     }
305 
306 
307     /**
308      * Returns the size of the value change computed by the
309      * <code>getNextValue</code>
310      * and <code>getPreviousValue</code> methods.
311      *
312      * @return the value of the <code>stepSize</code> property
313      * @see #setStepSize
314      */
315     public Number getStepSize() {
316         return stepSize;
317     }
318 
319 
320     private Number incrValue(int dir)
321     {
322         Number newValue;
323         if ((value instanceof Float) || (value instanceof Double)) {
324             double v = value.doubleValue() + (stepSize.doubleValue() * (double)dir);
325             if (value instanceof Double) {
326                 newValue = new Double(v);
327             }
328             else {
329                 newValue = new Float(v);
330             }
331         }
332         else {
333             long v = value.longValue() + (stepSize.longValue() * (long)dir);
334 
335             if (value instanceof Long) {
336                 newValue = Long.valueOf(v);
337             }
338             else if (value instanceof Integer) {
339                 newValue = Integer.valueOf((int)v);
340             }
341             else if (value instanceof Short) {
342                 newValue = Short.valueOf((short)v);
343             }
344             else {
345                 newValue = Byte.valueOf((byte)v);
346             }
347         }
348 
349         if ((maximum != null) && (maximum.compareTo(newValue) < 0)) {
350             return null;
351         }
352         if ((minimum != null) && (minimum.compareTo(newValue) > 0)) {
353             return null;
354         }
355         else {
356             return newValue;
357         }
358     }
359 
360 
361     /**
362      * Returns the next number in the sequence.
363      *
364      * @return <code>value + stepSize</code> or <code>null</code> if the sum
365      *     exceeds <code>maximum</code>.
366      *
367      * @see SpinnerModel#getNextValue
368      * @see #getPreviousValue
369      * @see #setStepSize
370      */
371     public Object getNextValue() {
372         return incrValue(+1);
373     }
374 
375 
376     /**
377      * Returns the previous number in the sequence.
378      *
379      * @return <code>value - stepSize</code>, or
380      *     <code>null</code> if the sum is less
381      *     than <code>minimum</code>.
382      *
383      * @see SpinnerModel#getPreviousValue
384      * @see #getNextValue
385      * @see #setStepSize
386      */
387     public Object getPreviousValue() {
388         return incrValue(-1);
389     }
390 
391 
392     /**
393      * Returns the value of the current element of the sequence.
394      *
395      * @return the value property
396      * @see #setValue
397      */
398     public Number getNumber() {
399         return value;
400     }
401 
402 
403     /**
404      * Returns the value of the current element of the sequence.
405      *
406      * @return the value property
407      * @see #setValue
408      * @see #getNumber
409      */
410     public Object getValue() {
411         return value;
412     }
413 
414 
415     /**
416      * Sets the current value for this sequence.  If <code>value</code> is
417      * <code>null</code>, or not a <code>Number</code>, an
418      * <code>IllegalArgumentException</code> is thrown.  No
419      * bounds checking is done here; the new value may invalidate the
420      * <code>(minimum &lt;= value &lt;= maximum)</code>
421      * invariant enforced by the constructors.   It's also possible to set
422      * the value to be something that wouldn't naturally occur in the sequence,
423      * i.e. a value that's not modulo the <code>stepSize</code>.
424      * This is to simplify updating the model, and to accommodate
425      * spinners that don't want to restrict values that have been
426      * directly entered by the user. Naturally, one should ensure that the
427      * <code>(minimum &lt;= value &lt;= maximum)</code> invariant is true
428      * before calling the <code>next</code>, <code>previous</code>, or
429      * <code>setValue</code> methods.
430      * <p>
431      * This method fires a <code>ChangeEvent</code> if the value has changed.
432      *
433      * @param value the current (non <code>null</code>) <code>Number</code>
434      *         for this sequence
435      * @throws IllegalArgumentException if <code>value</code> is
436      *         <code>null</code> or not a <code>Number</code>
437      * @see #getNumber
438      * @see #getValue
439      * @see SpinnerModel#addChangeListener
440      */
441     public void setValue(Object value) {
442         if ((value == null) || !(value instanceof Number)) {
443             throw new IllegalArgumentException("illegal value");
444         }
445         if (!value.equals(this.value)) {
446             this.value = (Number)value;
447             fireStateChanged();
448         }
449     }
450 }