1 /* 2 * Copyright (c) 2000, 2004, 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 <code>Date</code>s. 34 * The upper and lower bounds of the sequence are defined by properties called 35 * <code>start</code> and <code>end</code> and the size 36 * of the increase or decrease computed by the <code>nextValue</code> 37 * and <code>previousValue</code> methods is defined by a property 38 * called <code>calendarField</code>. The <code>start</code> 39 * and <code>end</code> properties can be <code>null</code> to 40 * indicate that the sequence has no lower or upper limit. 41 * <p> 42 * The value of the <code>calendarField</code> property must be one of the 43 * <code>java.util.Calendar</code> constants that specify a field 44 * within a <code>Calendar</code>. The <code>getNextValue</code> 45 * and <code>getPreviousValue</code> 46 * methods change the date forward or backwards by this amount. 47 * For example, if <code>calendarField</code> is <code>Calendar.DAY_OF_WEEK</code>, 48 * then <code>nextValue</code> produces a <code>Date</code> that's 24 49 * hours after the current <code>value</code>, and <code>previousValue</code> 50 * produces a <code>Date</code> that's 24 hours earlier. 51 * <p> 52 * The legal values for <code>calendarField</code> are: 53 * <ul> 54 * <li><code>Calendar.ERA</code> 55 * <li><code>Calendar.YEAR</code> 56 * <li><code>Calendar.MONTH</code> 57 * <li><code>Calendar.WEEK_OF_YEAR</code> 58 * <li><code>Calendar.WEEK_OF_MONTH</code> 59 * <li><code>Calendar.DAY_OF_MONTH</code> 60 * <li><code>Calendar.DAY_OF_YEAR</code> 61 * <li><code>Calendar.DAY_OF_WEEK</code> 62 * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code> 63 * <li><code>Calendar.AM_PM</code> 64 * <li><code>Calendar.HOUR</code> 65 * <li><code>Calendar.HOUR_OF_DAY</code> 66 * <li><code>Calendar.MINUTE</code> 67 * <li><code>Calendar.SECOND</code> 68 * <li><code>Calendar.MILLISECOND</code> 69 * </ul> 70 * However some UIs may set the calendarField before committing the edit 71 * to spin the field under the cursor. If you only want one field to 72 * spin you can subclass and ignore the setCalendarField calls. 73 * <p> 74 * This model inherits a <code>ChangeListener</code>. The 75 * <code>ChangeListeners</code> are notified whenever the models 76 * <code>value</code>, <code>calendarField</code>, 77 * <code>start</code>, or <code>end</code> properties changes. 78 * 79 * @see JSpinner 80 * @see SpinnerModel 81 * @see AbstractSpinnerModel 82 * @see SpinnerListModel 83 * @see SpinnerNumberModel 84 * @see Calendar#add 85 * 86 * @author Hans Muller 87 * @since 1.4 88 */ 89 public class SpinnerDateModel extends AbstractSpinnerModel implements Serializable 90 { 91 private Comparable start, end; 92 private Calendar value; 93 private int calendarField; 94 95 96 private boolean calendarFieldOK(int calendarField) { 97 switch(calendarField) { 98 case Calendar.ERA: 99 case Calendar.YEAR: 100 case Calendar.MONTH: 101 case Calendar.WEEK_OF_YEAR: 102 case Calendar.WEEK_OF_MONTH: 103 case Calendar.DAY_OF_MONTH: 104 case Calendar.DAY_OF_YEAR: 105 case Calendar.DAY_OF_WEEK: 106 case Calendar.DAY_OF_WEEK_IN_MONTH: 107 case Calendar.AM_PM: 108 case Calendar.HOUR: 109 case Calendar.HOUR_OF_DAY: 110 case Calendar.MINUTE: 111 case Calendar.SECOND: 112 case Calendar.MILLISECOND: 113 return true; 114 default: 115 return false; 116 } 117 } 118 119 120 /** 121 * Creates a <code>SpinnerDateModel</code> that represents a sequence of dates 122 * between <code>start</code> and <code>end</code>. The 123 * <code>nextValue</code> and <code>previousValue</code> methods 124 * compute elements of the sequence by advancing or reversing 125 * the current date <code>value</code> by the 126 * <code>calendarField</code> time unit. For a precise description 127 * of what it means to increment or decrement a <code>Calendar</code> 128 * <code>field</code>, see the <code>add</code> method in 129 * <code>java.util.Calendar</code>. 130 * <p> 131 * The <code>start</code> and <code>end</code> parameters can be 132 * <code>null</code> to indicate that the range doesn't have an 133 * upper or lower bound. If <code>value</code> or 134 * <code>calendarField</code> is <code>null</code>, or if both 135 * <code>start</code> and <code>end</code> are specified and 136 * <code>minimum > maximum</code> then an 137 * <code>IllegalArgumentException</code> is thrown. 138 * Similarly if <code>(minimum <= value <= maximum)</code> is false, 139 * an IllegalArgumentException is thrown. 140 * 141 * @param value the current (non <code>null</code>) value of the model 142 * @param start the first date in the sequence or <code>null</code> 143 * @param end the last date in the sequence or <code>null</code> 144 * @param calendarField one of 145 * <ul> 146 * <li><code>Calendar.ERA</code> 147 * <li><code>Calendar.YEAR</code> 148 * <li><code>Calendar.MONTH</code> 149 * <li><code>Calendar.WEEK_OF_YEAR</code> 150 * <li><code>Calendar.WEEK_OF_MONTH</code> 151 * <li><code>Calendar.DAY_OF_MONTH</code> 152 * <li><code>Calendar.DAY_OF_YEAR</code> 153 * <li><code>Calendar.DAY_OF_WEEK</code> 154 * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code> 155 * <li><code>Calendar.AM_PM</code> 156 * <li><code>Calendar.HOUR</code> 157 * <li><code>Calendar.HOUR_OF_DAY</code> 158 * <li><code>Calendar.MINUTE</code> 159 * <li><code>Calendar.SECOND</code> 160 * <li><code>Calendar.MILLISECOND</code> 161 * </ul> 162 * 163 * @throws IllegalArgumentException if <code>value</code> or 164 * <code>calendarField</code> are <code>null</code>, 165 * if <code>calendarField</code> isn't valid, 166 * or if the following expression is 167 * false: <code>(start <= value <= end)</code>. 168 * 169 * @see Calendar#add 170 * @see #setValue 171 * @see #setStart 172 * @see #setEnd 173 * @see #setCalendarField 174 */ 175 public SpinnerDateModel(Date value, Comparable start, Comparable end, int calendarField) { 176 if (value == null) { 177 throw new IllegalArgumentException("value is null"); 178 } 179 if (!calendarFieldOK(calendarField)) { 180 throw new IllegalArgumentException("invalid calendarField"); 181 } 182 if (!(((start == null) || (start.compareTo(value) <= 0)) && 183 ((end == null) || (end.compareTo(value) >= 0)))) { 184 throw new IllegalArgumentException("(start <= value <= end) is false"); 185 } 186 this.value = Calendar.getInstance(); 187 this.start = start; 188 this.end = end; 189 this.calendarField = calendarField; 190 191 this.value.setTime(value); 192 } 193 194 195 /** 196 * Constructs a <code>SpinnerDateModel</code> whose initial 197 * <code>value</code> is the current date, <code>calendarField</code> 198 * is equal to <code>Calendar.DAY_OF_MONTH</code>, and for which 199 * there are no <code>start</code>/<code>end</code> limits. 200 */ 201 public SpinnerDateModel() { 202 this(new Date(), null, null, Calendar.DAY_OF_MONTH); 203 } 204 205 206 /** 207 * Changes the lower limit for Dates in this sequence. 208 * If <code>start</code> is <code>null</code>, 209 * then there is no lower limit. No bounds checking is done here: 210 * the new start value may invalidate the 211 * <code>(start <= value <= end)</code> 212 * invariant enforced by the constructors. This is to simplify updating 213 * the model. Naturally one should ensure that the invariant is true 214 * before calling the <code>nextValue</code>, <code>previousValue</code>, 215 * or <code>setValue</code> methods. 216 * <p> 217 * Typically this property is a <code>Date</code> however it's possible to use 218 * a <code>Comparable</code> with a <code>compareTo</code> method for Dates. 219 * For example <code>start</code> might be an instance of a class like this: 220 * <pre> 221 * MyStartDate implements Comparable { 222 * long t = 12345; 223 * public int compareTo(Date d) { 224 * return (t < d.getTime() ? -1 : (t == d.getTime() ? 0 : 1)); 225 * } 226 * public int compareTo(Object o) { 227 * return compareTo((Date)o); 228 * } 229 * } 230 * </pre> 231 * Note that the above example will throw a <code>ClassCastException</code> 232 * if the <code>Object</code> passed to <code>compareTo(Object)</code> 233 * is not a <code>Date</code>. 234 * <p> 235 * This method fires a <code>ChangeEvent</code> if the 236 * <code>start</code> has changed. 237 * 238 * @param start defines the first date in the sequence 239 * @see #getStart 240 * @see #setEnd 241 * @see #addChangeListener 242 */ 243 public void setStart(Comparable start) { 244 if ((start == null) ? (this.start != null) : !start.equals(this.start)) { 245 this.start = start; 246 fireStateChanged(); 247 } 248 } 249 250 251 /** 252 * Returns the first <code>Date</code> in the sequence. 253 * 254 * @return the value of the <code>start</code> property 255 * @see #setStart 256 */ 257 public Comparable getStart() { 258 return start; 259 } 260 261 262 /** 263 * Changes the upper limit for <code>Date</code>s in this sequence. 264 * If <code>start</code> is <code>null</code>, then there is no upper 265 * limit. No bounds checking is done here: the new 266 * start value may invalidate the <code>(start <= value <= end)</code> 267 * invariant enforced by the constructors. This is to simplify updating 268 * the model. Naturally, one should ensure that the invariant is true 269 * before calling the <code>nextValue</code>, <code>previousValue</code>, 270 * or <code>setValue</code> methods. 271 * <p> 272 * Typically this property is a <code>Date</code> however it's possible to use 273 * <code>Comparable</code> with a <code>compareTo</code> method for 274 * <code>Date</code>s. See <code>setStart</code> for an example. 275 * <p> 276 * This method fires a <code>ChangeEvent</code> if the <code>end</code> 277 * has changed. 278 * 279 * @param end defines the last date in the sequence 280 * @see #getEnd 281 * @see #setStart 282 * @see #addChangeListener 283 */ 284 public void setEnd(Comparable end) { 285 if ((end == null) ? (this.end != null) : !end.equals(this.end)) { 286 this.end = end; 287 fireStateChanged(); 288 } 289 } 290 291 292 /** 293 * Returns the last <code>Date</code> in the sequence. 294 * 295 * @return the value of the <code>end</code> property 296 * @see #setEnd 297 */ 298 public Comparable getEnd() { 299 return end; 300 } 301 302 303 /** 304 * Changes the size of the date value change computed 305 * by the <code>nextValue</code> and <code>previousValue</code> methods. 306 * The <code>calendarField</code> parameter must be one of the 307 * <code>Calendar</code> field constants like <code>Calendar.MONTH</code> 308 * or <code>Calendar.MINUTE</code>. 309 * The <code>nextValue</code> and <code>previousValue</code> methods 310 * simply move the specified <code>Calendar</code> field forward or backward 311 * by one unit with the <code>Calendar.add</code> method. 312 * You should use this method with care as some UIs may set the 313 * calendarField before committing the edit to spin the field under 314 * the cursor. If you only want one field to spin you can subclass 315 * and ignore the setCalendarField calls. 316 * 317 * @param calendarField one of 318 * <ul> 319 * <li><code>Calendar.ERA</code> 320 * <li><code>Calendar.YEAR</code> 321 * <li><code>Calendar.MONTH</code> 322 * <li><code>Calendar.WEEK_OF_YEAR</code> 323 * <li><code>Calendar.WEEK_OF_MONTH</code> 324 * <li><code>Calendar.DAY_OF_MONTH</code> 325 * <li><code>Calendar.DAY_OF_YEAR</code> 326 * <li><code>Calendar.DAY_OF_WEEK</code> 327 * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code> 328 * <li><code>Calendar.AM_PM</code> 329 * <li><code>Calendar.HOUR</code> 330 * <li><code>Calendar.HOUR_OF_DAY</code> 331 * <li><code>Calendar.MINUTE</code> 332 * <li><code>Calendar.SECOND</code> 333 * <li><code>Calendar.MILLISECOND</code> 334 * </ul> 335 * <p> 336 * This method fires a <code>ChangeEvent</code> if the 337 * <code>calendarField</code> has changed. 338 * 339 * @see #getCalendarField 340 * @see #getNextValue 341 * @see #getPreviousValue 342 * @see Calendar#add 343 * @see #addChangeListener 344 */ 345 public void setCalendarField(int calendarField) { 346 if (!calendarFieldOK(calendarField)) { 347 throw new IllegalArgumentException("invalid calendarField"); 348 } 349 if (calendarField != this.calendarField) { 350 this.calendarField = calendarField; 351 fireStateChanged(); 352 } 353 } 354 355 356 /** 357 * Returns the <code>Calendar</code> field that is added to or subtracted from 358 * by the <code>nextValue</code> and <code>previousValue</code> methods. 359 * 360 * @return the value of the <code>calendarField</code> property 361 * @see #setCalendarField 362 */ 363 public int getCalendarField() { 364 return calendarField; 365 } 366 367 368 /** 369 * Returns the next <code>Date</code> in the sequence, or <code>null</code> if 370 * the next date is after <code>end</code>. 371 * 372 * @return the next <code>Date</code> in the sequence, or <code>null</code> if 373 * the next date is after <code>end</code>. 374 * 375 * @see SpinnerModel#getNextValue 376 * @see #getPreviousValue 377 * @see #setCalendarField 378 */ 379 public Object getNextValue() { 380 Calendar cal = Calendar.getInstance(); 381 cal.setTime(value.getTime()); 382 cal.add(calendarField, 1); 383 Date next = cal.getTime(); 384 return ((end == null) || (end.compareTo(next) >= 0)) ? next : null; 385 } 386 387 388 /** 389 * Returns the previous <code>Date</code> in the sequence, or <code>null</code> 390 * if the previous date is before <code>start</code>. 391 * 392 * @return the previous <code>Date</code> in the sequence, or 393 * <code>null</code> if the previous date 394 * is before <code>start</code> 395 * 396 * @see SpinnerModel#getPreviousValue 397 * @see #getNextValue 398 * @see #setCalendarField 399 */ 400 public Object getPreviousValue() { 401 Calendar cal = Calendar.getInstance(); 402 cal.setTime(value.getTime()); 403 cal.add(calendarField, -1); 404 Date prev = cal.getTime(); 405 return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null; 406 } 407 408 409 /** 410 * Returns the current element in this sequence of <code>Date</code>s. 411 * This method is equivalent to <code>(Date)getValue</code>. 412 * 413 * @return the <code>value</code> property 414 * @see #setValue 415 */ 416 public Date getDate() { 417 return value.getTime(); 418 } 419 420 421 /** 422 * Returns the current element in this sequence of <code>Date</code>s. 423 * 424 * @return the <code>value</code> property 425 * @see #setValue 426 * @see #getDate 427 */ 428 public Object getValue() { 429 return value.getTime(); 430 } 431 432 433 /** 434 * Sets the current <code>Date</code> for this sequence. 435 * If <code>value</code> is <code>null</code>, 436 * an <code>IllegalArgumentException</code> is thrown. No bounds 437 * checking is done here: 438 * the new value may invalidate the <code>(start <= value < end)</code> 439 * invariant enforced by the constructors. Naturally, one should ensure 440 * that the <code>(start <= value <= maximum)</code> invariant is true 441 * before calling the <code>nextValue</code>, <code>previousValue</code>, 442 * or <code>setValue</code> methods. 443 * <p> 444 * This method fires a <code>ChangeEvent</code> if the 445 * <code>value</code> has changed. 446 * 447 * @param value the current (non <code>null</code>) 448 * <code>Date</code> for this sequence 449 * @throws IllegalArgumentException if value is <code>null</code> 450 * or not a <code>Date</code> 451 * @see #getDate 452 * @see #getValue 453 * @see #addChangeListener 454 */ 455 public void setValue(Object value) { 456 if ((value == null) || !(value instanceof Date)) { 457 throw new IllegalArgumentException("illegal value"); 458 } 459 if (!value.equals(this.value.getTime())) { 460 this.value.setTime((Date)value); 461 fireStateChanged(); 462 } 463 } 464 }