blob: e9b89b68ecf21b63aecc8c4d7bb36172e586828b [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2004 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing;
27
28import java.util.*;
29import 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*/
83public 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>mininum &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(new Integer(value), new Integer(minimum), new Integer(maximum), new Integer(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(new Integer(0), null, null, new Integer(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 < 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 <= value < 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 = new Long(v);
337 }
338 else if (value instanceof Integer) {
339 newValue = new Integer((int)v);
340 }
341 else if (value instanceof Short) {
342 newValue = new Short((short)v);
343 }
344 else {
345 newValue = new Byte((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}