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