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 }