001/* 002 * $Id: AbstractDateSelectionModel.java 3927 2011-02-22 16:34:11Z kleopatra $ 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 */ 021package org.jdesktop.swingx.calendar; 022 023import java.util.Calendar; 024import java.util.Collections; 025import java.util.Date; 026import java.util.List; 027import java.util.Locale; 028import java.util.SortedSet; 029import java.util.TimeZone; 030import java.util.TreeSet; 031 032import org.jdesktop.swingx.event.DateSelectionEvent; 033import org.jdesktop.swingx.event.DateSelectionListener; 034import org.jdesktop.swingx.event.EventListenerMap; 035import 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 */ 043public 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 @Override 084 public Calendar getCalendar() { 085 return (Calendar) calendar.clone(); 086 } 087 088 /** 089 * {@inheritDoc} 090 */ 091 @Override 092 public int getFirstDayOfWeek() { 093 return calendar.getFirstDayOfWeek(); 094 } 095 096 /** 097 * {@inheritDoc} 098 */ 099 @Override 100 public void setFirstDayOfWeek(final int firstDayOfWeek) { 101 if (firstDayOfWeek == getFirstDayOfWeek()) return; 102 calendar.setFirstDayOfWeek(firstDayOfWeek); 103 fireValueChanged(EventType.CALENDAR_CHANGED); 104 } 105 106 /** 107 * {@inheritDoc} 108 */ 109 @Override 110 public int getMinimalDaysInFirstWeek() { 111 return calendar.getMinimalDaysInFirstWeek(); 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override 118 public void setMinimalDaysInFirstWeek(int minimalDays) { 119 if (minimalDays == getMinimalDaysInFirstWeek()) return; 120 calendar.setMinimalDaysInFirstWeek(minimalDays); 121 fireValueChanged(EventType.CALENDAR_CHANGED); 122 } 123 124 125 /** 126 * {@inheritDoc} 127 */ 128 @Override 129 public TimeZone getTimeZone() { 130 return calendar.getTimeZone(); 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public void setTimeZone(TimeZone timeZone) { 138 if (getTimeZone().equals(timeZone)) return; 139 TimeZone oldTimeZone = getTimeZone(); 140 calendar.setTimeZone(timeZone); 141 adjustDatesToTimeZone(oldTimeZone); 142 fireValueChanged(EventType.CALENDAR_CHANGED); 143 } 144 145 /** 146 * Adjusts all stored dates to a new time zone. 147 * This method is called after the change had been made. <p> 148 * 149 * This implementation resets all dates to null, clears everything. 150 * Subclasses may override to really map to the new time zone. 151 * 152 * @param oldTimeZone the old time zone 153 * 154 */ 155 protected void adjustDatesToTimeZone(TimeZone oldTimeZone) { 156 clearSelection(); 157 setLowerBound(null); 158 setUpperBound(null); 159 setUnselectableDates(EMPTY_DATES); 160 } 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override 166 public Locale getLocale() { 167 return locale; 168 } 169 170 /** 171 * {@inheritDoc} 172 */ 173 @Override 174 public void setLocale(Locale locale) { 175 if (locale == null) { 176 locale = Locale.getDefault(); 177 } 178 if (locale.equals(getLocale())) return; 179 this.locale = locale; 180 if (calendar != null) { 181 calendar = Calendar.getInstance(calendar.getTimeZone(), locale); 182 } else { 183 calendar = Calendar.getInstance(locale); 184 } 185 fireValueChanged(EventType.CALENDAR_CHANGED); 186 } 187 188//------------------- utility methods 189 190 /** 191 * Returns the start of the day of the given date in this model's calendar. 192 * NOTE: the calendar is changed by this operation. 193 * 194 * @param date the Date to get the start for. 195 * @return the Date representing the start of the day of the input date. 196 */ 197 protected Date startOfDay(Date date) { 198 return CalendarUtils.startOfDay(calendar, date); 199 } 200 201 /** 202 * Returns the end of the day of the given date in this model's calendar. 203 * NOTE: the calendar is changed by this operation. 204 * 205 * @param date the Date to get the start for. 206 * @return the Date representing the end of the day of the input date. 207 */ 208 protected Date endOfDay(Date date) { 209 return CalendarUtils.endOfDay(calendar, date); 210 } 211 212 /** 213 * Returns a boolean indicating whether the given dates are on the same day in 214 * the coordinates of the model's calendar. 215 * 216 * @param selected one of the dates to check, must not be null. 217 * @param compare the other of the dates to check, must not be null. 218 * @return true if both dates represent the same day in this model's calendar. 219 */ 220 protected boolean isSameDay(Date selected, Date compare) { 221 return startOfDay(selected).equals(startOfDay(compare)); 222 } 223 224//------------------- bounds 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public Date getUpperBound() { 231 return upperBound; 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public void setUpperBound(Date upperBound) { 239 if (upperBound != null) { 240 upperBound = getNormalizedDate(upperBound); 241 } 242 if (CalendarUtils.areEqual(upperBound, getUpperBound())) 243 return; 244 this.upperBound = upperBound; 245 if (this.upperBound != null && !isSelectionEmpty()) { 246 long justAboveUpperBoundMs = this.upperBound.getTime() + 1; 247 removeSelectionInterval(new Date(justAboveUpperBoundMs), 248 getLastSelectionDate()); 249 } 250 fireValueChanged(EventType.UPPER_BOUND_CHANGED); 251 } 252 253 /** 254 * {@inheritDoc} 255 */ 256 @Override 257 public Date getLowerBound() { 258 return lowerBound; 259 } 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override 265 public void setLowerBound(Date lowerBound) { 266 if (lowerBound != null) { 267 lowerBound = getNormalizedDate(lowerBound); 268 } 269 if (CalendarUtils.areEqual(lowerBound, getLowerBound())) 270 return; 271 this.lowerBound = lowerBound; 272 if (this.lowerBound != null && !isSelectionEmpty()) { 273 // Remove anything below the lower bound 274 long justBelowLowerBoundMs = this.lowerBound.getTime() - 1; 275 removeSelectionInterval(getFirstSelectionDate(), new Date( 276 justBelowLowerBoundMs)); 277 } 278 fireValueChanged(EventType.LOWER_BOUND_CHANGED); 279 } 280 281 282 /** 283 * {@inheritDoc} 284 */ 285 @Override 286 public boolean isAdjusting() { 287 return adjusting; 288 } 289 290 /** 291 * {@inheritDoc} 292 */ 293 @Override 294 public void setAdjusting(boolean adjusting) { 295 if (adjusting == isAdjusting()) return; 296 this.adjusting = adjusting; 297 fireValueChanged(adjusting ? EventType.ADJUSTING_STARTED : EventType.ADJUSTING_STOPPED); 298 299 } 300 301//----------------- notification 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public void addDateSelectionListener(DateSelectionListener l) { 307 listenerMap.add(DateSelectionListener.class, l); 308 } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override 314 public void removeDateSelectionListener(DateSelectionListener l) { 315 listenerMap.remove(DateSelectionListener.class, l); 316 } 317 318 public List<DateSelectionListener> getDateSelectionListeners() { 319 return listenerMap.getListeners(DateSelectionListener.class); 320 } 321 322 protected void fireValueChanged(DateSelectionEvent.EventType eventType) { 323 List<DateSelectionListener> listeners = getDateSelectionListeners(); 324 DateSelectionEvent e = null; 325 326 for (DateSelectionListener listener : listeners) { 327 if (e == null) { 328 e = new DateSelectionEvent(this, eventType, isAdjusting()); 329 } 330 listener.valueChanged(e); 331 } 332 } 333 334 335 336}