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 26 package javax.swing; 27 28 import java.util.*; 29 import java.io.Serializable; 30 31 32 /** 33 * A {@code SpinnerModel} for sequences of {@code Date}s. 34 * The upper and lower bounds of the sequence are defined by properties called 35 * {@code start} and {@code end} and the size 36 * of the increase or decrease computed by the {@code nextValue} 37 * and {@code previousValue} methods is defined by a property 38 * called {@code calendarField}. The {@code start} 39 * and {@code end} properties can be {@code null} to 40 * indicate that the sequence has no lower or upper limit. 41 * <p> 42 * The value of the {@code calendarField} property must be one of the 43 * {@code java.util.Calendar} constants that specify a field 44 * within a {@code Calendar}. The {@code getNextValue} 45 * and {@code getPreviousValue} 46 * methods change the date forward or backwards by this amount. 47 * For example, if {@code calendarField} is {@code Calendar.DAY_OF_WEEK}, 48 * then {@code nextValue} produces a {@code Date} that's 24 49 * hours after the current {@code value}, and {@code previousValue} 50 * produces a {@code Date} that's 24 hours earlier. 51 * <p> 52 * The legal values for {@code calendarField} are: 53 * <ul> 54 * <li>{@code Calendar.ERA} 55 * <li>{@code Calendar.YEAR} 56 * <li>{@code Calendar.MONTH} 57 * <li>{@code Calendar.WEEK_OF_YEAR} 58 * <li>{@code Calendar.WEEK_OF_MONTH} 59 * <li>{@code Calendar.DAY_OF_MONTH} 60 * <li>{@code Calendar.DAY_OF_YEAR} 61 * <li>{@code Calendar.DAY_OF_WEEK} 62 * <li>{@code Calendar.DAY_OF_WEEK_IN_MONTH} 63 * <li>{@code Calendar.AM_PM} 64 * <li>{@code Calendar.HOUR} 65 * <li>{@code Calendar.HOUR_OF_DAY} 66 * <li>{@code Calendar.MINUTE} 67 * <li>{@code Calendar.SECOND} 68 * <li>{@code Calendar.MILLISECOND} 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}. The 75 * {@code ChangeListeners} are notified whenever the models 76 * {@code value}, {@code calendarField}, 77 * {@code start}, or {@code end} 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 90 public 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} that represents a sequence of dates 123 * between {@code start} and {@code end}. The 124 * {@code nextValue} and {@code previousValue} methods 125 * compute elements of the sequence by advancing or reversing 126 * the current date {@code value} by the 127 * {@code calendarField} time unit. For a precise description 128 * of what it means to increment or decrement a {@code Calendar} 129 * {@code field}, see the {@code add} method in 130 * {@code java.util.Calendar}. 131 * <p> 132 * The {@code start} and {@code end} parameters can be 133 * {@code null} to indicate that the range doesn't have an 134 * upper or lower bound. If {@code value} or 135 * {@code calendarField} is {@code null}, or if both 136 * {@code start} and {@code end} are specified and 137 * {@code minimum > maximum} then an 138 * {@code IllegalArgumentException} is thrown. 139 * Similarly if {@code (minimum <= value <= maximum)} is false, 140 * an IllegalArgumentException is thrown. 141 * 142 * @param value the current (non {@code null}) value of the model 143 * @param start the first date in the sequence or {@code null} 144 * @param end the last date in the sequence or {@code null} 145 * @param calendarField one of 146 * <ul> 147 * <li>{@code Calendar.ERA} 148 * <li>{@code Calendar.YEAR} 149 * <li>{@code Calendar.MONTH} 150 * <li>{@code Calendar.WEEK_OF_YEAR} 151 * <li>{@code Calendar.WEEK_OF_MONTH} 152 * <li>{@code Calendar.DAY_OF_MONTH} 153 * <li>{@code Calendar.DAY_OF_YEAR} 154 * <li>{@code Calendar.DAY_OF_WEEK} 155 * <li>{@code Calendar.DAY_OF_WEEK_IN_MONTH} 156 * <li>{@code Calendar.AM_PM} 157 * <li>{@code Calendar.HOUR} 158 * <li>{@code Calendar.HOUR_OF_DAY} 159 * <li>{@code Calendar.MINUTE} 160 * <li>{@code Calendar.SECOND} 161 * <li>{@code Calendar.MILLISECOND} 162 * </ul> 163 * 164 * @throws IllegalArgumentException if {@code value} or 165 * {@code calendarField} are {@code null}, 166 * if {@code calendarField} isn't valid, 167 * or if the following expression is 168 * false: {@code (start <= value <= end)}. 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} whose initial 198 * {@code value} is the current date, {@code calendarField} 199 * is equal to {@code Calendar.DAY_OF_MONTH}, and for which 200 * there are no {@code start}/{@code end} 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} is {@code null}, 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)} 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 previousValue}, 216 * or {@code setValue} methods. 217 * <p> 218 * Typically this property is a {@code Date} however it's possible to use 219 * a {@code Comparable} with a {@code compareTo} method for Dates. 220 * For example {@code start} 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} 233 * if the {@code Object} passed to {@code compareTo(Object)} 234 * is not a {@code Date}. 235 * <p> 236 * This method fires a {@code ChangeEvent} if the 237 * {@code start} 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} in the sequence. 254 * 255 * @return the value of the {@code start} 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}s in this sequence. 265 * If {@code start} is {@code null}, 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)} 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 previousValue}, 271 * or {@code setValue} methods. 272 * <p> 273 * Typically this property is a {@code Date} however it's possible to use 274 * {@code Comparable} with a {@code compareTo} method for 275 * {@code Date}s. See {@code setStart} for an example. 276 * <p> 277 * This method fires a {@code ChangeEvent} if the {@code end} 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} in the sequence. 295 * 296 * @return the value of the {@code end} 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} and {@code previousValue} methods. 307 * The {@code calendarField} parameter must be one of the 308 * {@code Calendar} field constants like {@code Calendar.MONTH} 309 * or {@code Calendar.MINUTE}. 310 * The {@code nextValue} and {@code previousValue} methods 311 * simply move the specified {@code Calendar} field forward or backward 312 * by one unit with the {@code Calendar.add} 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} 321 * <li>{@code Calendar.YEAR} 322 * <li>{@code Calendar.MONTH} 323 * <li>{@code Calendar.WEEK_OF_YEAR} 324 * <li>{@code Calendar.WEEK_OF_MONTH} 325 * <li>{@code Calendar.DAY_OF_MONTH} 326 * <li>{@code Calendar.DAY_OF_YEAR} 327 * <li>{@code Calendar.DAY_OF_WEEK} 328 * <li>{@code Calendar.DAY_OF_WEEK_IN_MONTH} 329 * <li>{@code Calendar.AM_PM} 330 * <li>{@code Calendar.HOUR} 331 * <li>{@code Calendar.HOUR_OF_DAY} 332 * <li>{@code Calendar.MINUTE} 333 * <li>{@code Calendar.SECOND} 334 * <li>{@code Calendar.MILLISECOND} 335 * </ul> 336 * <p> 337 * This method fires a {@code ChangeEvent} if the 338 * {@code calendarField} 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} field that is added to or subtracted from 359 * by the {@code nextValue} and {@code previousValue} methods. 360 * 361 * @return the value of the {@code calendarField} property 362 * @see #setCalendarField 363 */ 364 public int getCalendarField() { 365 return calendarField; 366 } 367 368 369 /** 370 * Returns the next {@code Date} in the sequence, or {@code null} if 371 * the next date is after {@code end}. 372 * 373 * @return the next {@code Date} in the sequence, or {@code null} if 374 * the next date is after {@code end}. 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} in the sequence, or {@code null} 391 * if the previous date is before {@code start}. 392 * 393 * @return the previous {@code Date} in the sequence, or 394 * {@code null} if the previous date 395 * is before {@code start} 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}s. 412 * This method is equivalent to {@code (Date)getValue}. 413 * 414 * @return the {@code value} 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}s. 424 * 425 * @return the {@code value} 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} for this sequence. 436 * If {@code value} is {@code null}, 437 * an {@code IllegalArgumentException} is thrown. No bounds 438 * checking is done here: 439 * the new value may invalidate the {@code (start <= value < end)} 440 * invariant enforced by the constructors. Naturally, one should ensure 441 * that the {@code (start <= value <= maximum)} invariant is true 442 * before calling the {@code nextValue}, {@code previousValue}, 443 * or {@code setValue} methods. 444 * <p> 445 * This method fires a {@code ChangeEvent} if the 446 * {@code value} has changed. 447 * 448 * @param value the current (non {@code null}) 449 * {@code Date} for this sequence 450 * @throws IllegalArgumentException if value is {@code null} 451 * or not a {@code Date} 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 } --- EOF ---