001 /*
002 * $Id: AbstractDateSelectionModel.java 3100 2008-10-14 22:33:10Z rah003 $
003 *
004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx.calendar;
022
023 import java.util.Calendar;
024 import java.util.Collections;
025 import java.util.Date;
026 import java.util.List;
027 import java.util.Locale;
028 import java.util.SortedSet;
029 import java.util.TimeZone;
030 import java.util.TreeSet;
031
032 import org.jdesktop.swingx.event.DateSelectionEvent;
033 import org.jdesktop.swingx.event.DateSelectionListener;
034 import org.jdesktop.swingx.event.EventListenerMap;
035 import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
036
037 /**
038 * Abstract base implementation of DateSelectionModel. Implements
039 * notification, Calendar related properties and lower/upper bounds.
040 *
041 * @author Jeanette Winzenburg
042 */
043 public abstract class AbstractDateSelectionModel implements DateSelectionModel {
044 public static final SortedSet<Date> EMPTY_DATES = Collections.unmodifiableSortedSet(new TreeSet<Date>());
045
046 protected EventListenerMap listenerMap;
047 protected boolean adjusting;
048 protected Calendar calendar;
049 protected Date upperBound;
050 protected Date lowerBound;
051
052 /**
053 * the locale used by the calendar. <p>
054 * NOTE: need to keep separately as a Calendar has no getter.
055 */
056 protected Locale locale;
057
058 /**
059 * Instantiates a DateSelectionModel with default locale.
060 */
061 public AbstractDateSelectionModel() {
062 this(null);
063 }
064
065 /**
066 * Instantiates a DateSelectionModel with the given locale. If the locale is
067 * null, the Locale's default is used.
068 *
069 * PENDING JW: fall back to JComponent.getDefaultLocale instead? We use this
070 * with components anyway?
071 *
072 * @param locale the Locale to use with this model, defaults to Locale.default()
073 * if null.
074 */
075 public AbstractDateSelectionModel(Locale locale) {
076 this.listenerMap = new EventListenerMap();
077 setLocale(locale);
078 }
079
080 /**
081 * {@inheritDoc}
082 */
083 public Calendar getCalendar() {
084 return (Calendar) calendar.clone();
085 }
086
087 /**
088 * {@inheritDoc}
089 */
090 public int getFirstDayOfWeek() {
091 return calendar.getFirstDayOfWeek();
092 }
093
094 /**
095 * {@inheritDoc}
096 */
097 public void setFirstDayOfWeek(final int firstDayOfWeek) {
098 if (firstDayOfWeek == getFirstDayOfWeek()) return;
099 calendar.setFirstDayOfWeek(firstDayOfWeek);
100 fireValueChanged(EventType.CALENDAR_CHANGED);
101 }
102
103 /**
104 * {@inheritDoc}
105 */
106 public int getMinimalDaysInFirstWeek() {
107 return calendar.getMinimalDaysInFirstWeek();
108 }
109
110 /**
111 * {@inheritDoc}
112 */
113 public void setMinimalDaysInFirstWeek(int minimalDays) {
114 if (minimalDays == getMinimalDaysInFirstWeek()) return;
115 calendar.setMinimalDaysInFirstWeek(minimalDays);
116 fireValueChanged(EventType.CALENDAR_CHANGED);
117 }
118
119
120 /**
121 * {@inheritDoc}
122 */
123 public TimeZone getTimeZone() {
124 return calendar.getTimeZone();
125 }
126
127 /**
128 * {@inheritDoc}
129 */
130 public void setTimeZone(TimeZone timeZone) {
131 if (getTimeZone().equals(timeZone)) return;
132 TimeZone oldTimeZone = getTimeZone();
133 calendar.setTimeZone(timeZone);
134 adjustDatesToTimeZone(oldTimeZone);
135 fireValueChanged(EventType.CALENDAR_CHANGED);
136 }
137
138 /**
139 * Adjusts all stored dates to a new time zone.
140 * This method is called after the change had been made. <p>
141 *
142 * This implementation resets all dates to null, clears everything.
143 * Subclasses may override to really map to the new time zone.
144 *
145 * @param oldTimeZone the old time zone
146 *
147 */
148 protected void adjustDatesToTimeZone(TimeZone oldTimeZone) {
149 clearSelection();
150 setLowerBound(null);
151 setUpperBound(null);
152 setUnselectableDates(EMPTY_DATES);
153 }
154
155 /**
156 * {@inheritDoc}
157 */
158 public Locale getLocale() {
159 return locale;
160 }
161
162 /**
163 * {@inheritDoc}
164 */
165 public void setLocale(Locale locale) {
166 if (locale == null) {
167 locale = Locale.getDefault();
168 }
169 if (locale.equals(getLocale())) return;
170 this.locale = locale;
171 if (calendar != null) {
172 calendar = Calendar.getInstance(calendar.getTimeZone(), locale);
173 } else {
174 calendar = Calendar.getInstance(locale);
175 }
176 fireValueChanged(EventType.CALENDAR_CHANGED);
177 }
178
179 //------------------- utility methods
180
181 /**
182 * Returns the start of the day of the given date in this model's calendar.
183 * NOTE: the calendar is changed by this operation.
184 *
185 * @param date the Date to get the start for.
186 * @return the Date representing the start of the day of the input date.
187 */
188 protected Date startOfDay(Date date) {
189 return CalendarUtils.startOfDay(calendar, date);
190 }
191
192 /**
193 * Returns the end of the day of the given date in this model's calendar.
194 * NOTE: the calendar is changed by this operation.
195 *
196 * @param date the Date to get the start for.
197 * @return the Date representing the end of the day of the input date.
198 */
199 protected Date endOfDay(Date date) {
200 return CalendarUtils.endOfDay(calendar, date);
201 }
202
203 /**
204 * Returns a boolean indicating whether the given dates are on the same day in
205 * the coordinates of the model's calendar.
206 *
207 * @param selected one of the dates to check, must not be null.
208 * @param compare the other of the dates to check, must not be null.
209 * @return true if both dates represent the same day in this model's calendar.
210 */
211 protected boolean isSameDay(Date selected, Date compare) {
212 return startOfDay(selected).equals(startOfDay(compare));
213 }
214
215 //------------------- bounds
216
217 /**
218 * {@inheritDoc}
219 */
220 public Date getUpperBound() {
221 return upperBound;
222 }
223
224 /**
225 * {@inheritDoc}
226 */
227 public void setUpperBound(Date upperBound) {
228 if (upperBound != null) {
229 upperBound = getNormalizedDate(upperBound);
230 }
231 if (CalendarUtils.areEqual(upperBound, getUpperBound()))
232 return;
233 this.upperBound = upperBound;
234 if (this.upperBound != null && !isSelectionEmpty()) {
235 long justAboveUpperBoundMs = this.upperBound.getTime() + 1;
236 removeSelectionInterval(new Date(justAboveUpperBoundMs),
237 getLastSelectionDate());
238 }
239 fireValueChanged(EventType.UPPER_BOUND_CHANGED);
240 }
241
242 /**
243 * {@inheritDoc}
244 */
245 public Date getLowerBound() {
246 return lowerBound;
247 }
248
249 /**
250 * {@inheritDoc}
251 */
252 public void setLowerBound(Date lowerBound) {
253 if (lowerBound != null) {
254 lowerBound = getNormalizedDate(lowerBound);
255 }
256 if (CalendarUtils.areEqual(lowerBound, getLowerBound()))
257 return;
258 this.lowerBound = lowerBound;
259 if (this.lowerBound != null && !isSelectionEmpty()) {
260 // Remove anything below the lower bound
261 long justBelowLowerBoundMs = this.lowerBound.getTime() - 1;
262 removeSelectionInterval(getFirstSelectionDate(), new Date(
263 justBelowLowerBoundMs));
264 }
265 fireValueChanged(EventType.LOWER_BOUND_CHANGED);
266 }
267
268
269 /**
270 * {@inheritDoc}
271 */
272 public boolean isAdjusting() {
273 return adjusting;
274 }
275
276 /**
277 * {@inheritDoc}
278 */
279 public void setAdjusting(boolean adjusting) {
280 if (adjusting == isAdjusting()) return;
281 this.adjusting = adjusting;
282 fireValueChanged(adjusting ? EventType.ADJUSTING_STARTED : EventType.ADJUSTING_STOPPED);
283
284 }
285
286 //----------------- notification
287 /**
288 * {@inheritDoc}
289 */
290 public void addDateSelectionListener(DateSelectionListener l) {
291 listenerMap.add(DateSelectionListener.class, l);
292 }
293
294 /**
295 * {@inheritDoc}
296 */
297 public void removeDateSelectionListener(DateSelectionListener l) {
298 listenerMap.remove(DateSelectionListener.class, l);
299 }
300
301 public List<DateSelectionListener> getDateSelectionListeners() {
302 return listenerMap.getListeners(DateSelectionListener.class);
303 }
304
305 protected void fireValueChanged(DateSelectionEvent.EventType eventType) {
306 List<DateSelectionListener> listeners = getDateSelectionListeners();
307 DateSelectionEvent e = null;
308
309 for (DateSelectionListener listener : listeners) {
310 if (e == null) {
311 e = new DateSelectionEvent(this, eventType, isAdjusting());
312 }
313 listener.valueChanged(e);
314 }
315 }
316
317
318
319 }