/* * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import java.util.*; import java.io.Serializable; /** * A SpinnerModel for sequences of Dates. * The upper and lower bounds of the sequence are defined by properties called * start and end and the size * of the increase or decrease computed by the nextValue * and previousValue methods is defined by a property * called calendarField. The start * and end properties can be null to * indicate that the sequence has no lower or upper limit. *

* The value of the calendarField property must be one of the * java.util.Calendar constants that specify a field * within a Calendar. The getNextValue * and getPreviousValue * methods change the date forward or backwards by this amount. * For example, if calendarField is Calendar.DAY_OF_WEEK, * then nextValue produces a Date that's 24 * hours after the current value, and previousValue * produces a Date that's 24 hours earlier. *

* The legal values for calendarField are: *

* However some UIs may set the calendarField before committing the edit * to spin the field under the cursor. If you only want one field to * spin you can subclass and ignore the setCalendarField calls. *

* This model inherits a ChangeListener. The * ChangeListeners are notified whenever the models * value, calendarField, * start, or end properties changes. * * @see JSpinner * @see SpinnerModel * @see AbstractSpinnerModel * @see SpinnerListModel * @see SpinnerNumberModel * @see Calendar#add * * @author Hans Muller * @since 1.4 */ @SuppressWarnings("serial") // Superclass is not serializable across versions public class SpinnerDateModel extends AbstractSpinnerModel implements Serializable { private Comparable start, end; private Calendar value; private int calendarField; private boolean calendarFieldOK(int calendarField) { switch(calendarField) { case Calendar.ERA: case Calendar.YEAR: case Calendar.MONTH: case Calendar.WEEK_OF_YEAR: case Calendar.WEEK_OF_MONTH: case Calendar.DAY_OF_MONTH: case Calendar.DAY_OF_YEAR: case Calendar.DAY_OF_WEEK: case Calendar.DAY_OF_WEEK_IN_MONTH: case Calendar.AM_PM: case Calendar.HOUR: case Calendar.HOUR_OF_DAY: case Calendar.MINUTE: case Calendar.SECOND: case Calendar.MILLISECOND: return true; default: return false; } } /** * Creates a SpinnerDateModel that represents a sequence of dates * between start and end. The * nextValue and previousValue methods * compute elements of the sequence by advancing or reversing * the current date value by the * calendarField time unit. For a precise description * of what it means to increment or decrement a Calendar * field, see the add method in * java.util.Calendar. *

* The start and end parameters can be * null to indicate that the range doesn't have an * upper or lower bound. If value or * calendarField is null, or if both * start and end are specified and * minimum > maximum then an * IllegalArgumentException is thrown. * Similarly if (minimum <= value <= maximum) is false, * an IllegalArgumentException is thrown. * * @param value the current (non null) value of the model * @param start the first date in the sequence or null * @param end the last date in the sequence or null * @param calendarField one of *

* * @throws IllegalArgumentException if value or * calendarField are null, * if calendarField isn't valid, * or if the following expression is * false: (start <= value <= end). * * @see Calendar#add * @see #setValue * @see #setStart * @see #setEnd * @see #setCalendarField */ public SpinnerDateModel(Date value, Comparable start, Comparable end, int calendarField) { if (value == null) { throw new IllegalArgumentException("value is null"); } if (!calendarFieldOK(calendarField)) { throw new IllegalArgumentException("invalid calendarField"); } if (!(((start == null) || (start.compareTo(value) <= 0)) && ((end == null) || (end.compareTo(value) >= 0)))) { throw new IllegalArgumentException("(start <= value <= end) is false"); } this.value = Calendar.getInstance(); this.start = start; this.end = end; this.calendarField = calendarField; this.value.setTime(value); } /** * Constructs a SpinnerDateModel whose initial * value is the current date, calendarField * is equal to Calendar.DAY_OF_MONTH, and for which * there are no start/end limits. */ public SpinnerDateModel() { this(new Date(), null, null, Calendar.DAY_OF_MONTH); } /** * Changes the lower limit for Dates in this sequence. * If start is null, * then there is no lower limit. No bounds checking is done here: * the new start value may invalidate the * (start <= value <= end) * invariant enforced by the constructors. This is to simplify updating * the model. Naturally one should ensure that the invariant is true * before calling the nextValue, previousValue, * or setValue methods. *

* Typically this property is a Date however it's possible to use * a Comparable with a compareTo method for Dates. * For example start might be an instance of a class like this: *

     * MyStartDate implements Comparable {
     *     long t = 12345;
     *     public int compareTo(Date d) {
     *            return (t < d.getTime() ? -1 : (t == d.getTime() ? 0 : 1));
     *     }
     *     public int compareTo(Object o) {
     *            return compareTo((Date)o);
     *     }
     * }
     * 
* Note that the above example will throw a ClassCastException * if the Object passed to compareTo(Object) * is not a Date. *

* This method fires a ChangeEvent if the * start has changed. * * @param start defines the first date in the sequence * @see #getStart * @see #setEnd * @see #addChangeListener */ public void setStart(Comparable start) { if ((start == null) ? (this.start != null) : !start.equals(this.start)) { this.start = start; fireStateChanged(); } } /** * Returns the first Date in the sequence. * * @return the value of the start property * @see #setStart */ public Comparable getStart() { return start; } /** * Changes the upper limit for Dates in this sequence. * If start is null, then there is no upper * limit. No bounds checking is done here: the new * start value may invalidate the (start <= value <= end) * invariant enforced by the constructors. This is to simplify updating * the model. Naturally, one should ensure that the invariant is true * before calling the nextValue, previousValue, * or setValue methods. *

* Typically this property is a Date however it's possible to use * Comparable with a compareTo method for * Dates. See setStart for an example. *

* This method fires a ChangeEvent if the end * has changed. * * @param end defines the last date in the sequence * @see #getEnd * @see #setStart * @see #addChangeListener */ public void setEnd(Comparable end) { if ((end == null) ? (this.end != null) : !end.equals(this.end)) { this.end = end; fireStateChanged(); } } /** * Returns the last Date in the sequence. * * @return the value of the end property * @see #setEnd */ public Comparable getEnd() { return end; } /** * Changes the size of the date value change computed * by the nextValue and previousValue methods. * The calendarField parameter must be one of the * Calendar field constants like Calendar.MONTH * or Calendar.MINUTE. * The nextValue and previousValue methods * simply move the specified Calendar field forward or backward * by one unit with the Calendar.add method. * You should use this method with care as some UIs may set the * calendarField before committing the edit to spin the field under * the cursor. If you only want one field to spin you can subclass * and ignore the setCalendarField calls. * * @param calendarField one of *

*

* This method fires a ChangeEvent if the * calendarField has changed. * * @see #getCalendarField * @see #getNextValue * @see #getPreviousValue * @see Calendar#add * @see #addChangeListener */ public void setCalendarField(int calendarField) { if (!calendarFieldOK(calendarField)) { throw new IllegalArgumentException("invalid calendarField"); } if (calendarField != this.calendarField) { this.calendarField = calendarField; fireStateChanged(); } } /** * Returns the Calendar field that is added to or subtracted from * by the nextValue and previousValue methods. * * @return the value of the calendarField property * @see #setCalendarField */ public int getCalendarField() { return calendarField; } /** * Returns the next Date in the sequence, or null if * the next date is after end. * * @return the next Date in the sequence, or null if * the next date is after end. * * @see SpinnerModel#getNextValue * @see #getPreviousValue * @see #setCalendarField */ public Object getNextValue() { Calendar cal = Calendar.getInstance(); cal.setTime(value.getTime()); cal.add(calendarField, 1); Date next = cal.getTime(); return ((end == null) || (end.compareTo(next) >= 0)) ? next : null; } /** * Returns the previous Date in the sequence, or null * if the previous date is before start. * * @return the previous Date in the sequence, or * null if the previous date * is before start * * @see SpinnerModel#getPreviousValue * @see #getNextValue * @see #setCalendarField */ public Object getPreviousValue() { Calendar cal = Calendar.getInstance(); cal.setTime(value.getTime()); cal.add(calendarField, -1); Date prev = cal.getTime(); return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null; } /** * Returns the current element in this sequence of Dates. * This method is equivalent to (Date)getValue. * * @return the value property * @see #setValue */ public Date getDate() { return value.getTime(); } /** * Returns the current element in this sequence of Dates. * * @return the value property * @see #setValue * @see #getDate */ public Object getValue() { return value.getTime(); } /** * Sets the current Date for this sequence. * If value is null, * an IllegalArgumentException is thrown. No bounds * checking is done here: * the new value may invalidate the (start <= value < end) * invariant enforced by the constructors. Naturally, one should ensure * that the (start <= value <= maximum) invariant is true * before calling the nextValue, previousValue, * or setValue methods. *

* This method fires a ChangeEvent if the * value has changed. * * @param value the current (non null) * Date for this sequence * @throws IllegalArgumentException if value is null * or not a Date * @see #getDate * @see #getValue * @see #addChangeListener */ public void setValue(Object value) { if ((value == null) || !(value instanceof Date)) { throw new IllegalArgumentException("illegal value"); } if (!value.equals(this.value.getTime())) { this.value.setTime((Date)value); fireStateChanged(); } } }