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