001/* 002 * $Id: SingleDaySelectionModel.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.Date; 024import java.util.Locale; 025import java.util.SortedSet; 026import java.util.TreeSet; 027 028import org.jdesktop.swingx.event.DateSelectionEvent.EventType; 029import org.jdesktop.swingx.util.Contract; 030 031/** 032 * DateSelectionModel which allows a single selection only. <p> 033 * 034 * Temporary quick & dirty class to explore requirements as needed by 035 * a DatePicker. Need to define the states more exactly. Currently 036 * 037 * <li> takes all Dates as-are (that is the normalized is the same as the given): 038 * selected, unselectable, lower/upper bounds 039 * <li> interprets any Date between the start/end of day of the selected as selected 040 * <li> interprets any Date between the start/end of an unselectable date as unselectable 041 * <li> interprets the lower/upper bounds as being the start/end of the given 042 * dates, that is any Date after the start of day of the lower and before the end of 043 * day of the upper is selectable. 044 * 045 * 046 * @author Jeanette Winzenburg 047 */ 048public class SingleDaySelectionModel extends AbstractDateSelectionModel { 049 050 private SortedSet<Date> selectedDates; 051 private SortedSet<Date> unselectableDates; 052 053 /** 054 * Instantiates a SingleDaySelectionModel with default locale. 055 */ 056 public SingleDaySelectionModel() { 057 this(null); 058 } 059 060 /** 061 * Instantiates a SingleSelectionModel with the given locale. If the locale is 062 * null, the Locale's default is used. 063 * 064 * PENDING JW: fall back to JComponent.getDefaultLocale instead? We use this 065 * with components anyway? 066 * 067 * @param locale the Locale to use with this model, defaults to Locale.default() 068 * if null. 069 */ 070 public SingleDaySelectionModel(Locale locale) { 071 super(locale); 072 this.selectedDates = new TreeSet<Date>(); 073 this.unselectableDates = new TreeSet<Date>(); 074 } 075 076 /** 077 * {@inheritDoc} 078 */ 079 @Override 080 public SelectionMode getSelectionMode() { 081 return SelectionMode.SINGLE_SELECTION; 082 } 083 084 /** 085 * {@inheritDoc}<p> 086 * 087 * Implemented to do nothing. 088 * 089 */ 090 @Override 091 public void setSelectionMode(final SelectionMode selectionMode) { 092 } 093 094 095 //---------------------- selection ops 096 /** 097 * {@inheritDoc} <p> 098 * 099 * Implemented to call setSelectionInterval with startDate for both 100 * parameters. 101 */ 102 @Override 103 public void addSelectionInterval(Date startDate, Date endDate) { 104 setSelection(startDate); 105 } 106 107 /** 108 * {@inheritDoc}<p> 109 * 110 * PENDING JW: define what happens if we have a selection but the interval 111 * isn't selectable. 112 */ 113 @Override 114 public void setSelectionInterval(Date startDate, Date endDate) { 115 setSelection(startDate); 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 @Override 122 public void removeSelectionInterval(Date startDate, Date endDate) { 123 Contract.asNotNull(startDate, "date must not be null"); 124 if (isSelectionEmpty()) return; 125 if (isSelectionInInterval(startDate, endDate)) { 126 selectedDates.clear(); 127 fireValueChanged(EventType.DATES_REMOVED); 128 } 129 } 130 131 /** 132 * Checks and returns whether the selected date is contained in the interval 133 * given by startDate/endDate. The selection must not be empty when 134 * calling this method. <p> 135 * 136 * This implementation interprets the interval between the start of the day 137 * of startDay to the end of the day of endDate. 138 * 139 * @param startDate the start of the interval, must not be null 140 * @param endDate the end of the interval, must not be null 141 * @return true if the selected date is contained in the interval 142 */ 143 protected boolean isSelectionInInterval(Date startDate, Date endDate) { 144 if (selectedDates.first().before(startOfDay(startDate)) 145 || (selectedDates.first().after(endOfDay(endDate)))) return false; 146 return true; 147 } 148 149 /** 150 * Selects the given date if it is selectable and not yet selected. 151 * Does nothing otherwise. 152 * If this operation changes the current selection, it will fire a 153 * DateSelectionEvent of type DATES_SET. 154 * 155 * @param date the Date to select, must not be null. 156 */ 157 protected void setSelection(Date date) { 158 Contract.asNotNull(date, "date must not be null"); 159 if (isSelectedStrict(date)) return; 160 if (isSelectable(date)) { 161 selectedDates.clear(); 162 // PENDING JW: use normalized 163 selectedDates.add(date); 164 fireValueChanged(EventType.DATES_SET); 165 } 166 } 167 168 /** 169 * Checks and returns whether the given date is contained in the selection. 170 * This differs from isSelected in that it tests for the exact (normalized) 171 * Date instead of for the same day. 172 * 173 * @param date the Date to test. 174 * @return true if the given date is contained in the selection, 175 * false otherwise 176 * 177 */ 178 private boolean isSelectedStrict(Date date) { 179 if (!isSelectionEmpty()) { 180 // PENDING JW: use normalized 181 return selectedDates.first().equals(date); 182 } 183 return false; 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 public Date getFirstSelectionDate() { 191 return isSelectionEmpty() ? null : selectedDates.first(); 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 public Date getLastSelectionDate() { 199 return isSelectionEmpty() ? null : selectedDates.last(); 200 } 201 202 /** 203 * Returns a boolean indicating whether the given date is selectable. 204 * 205 * @param date the date to check for selectable, must not be null. 206 * @return true if the given date is selectable, false if not. 207 */ 208 public boolean isSelectable(Date date) { 209 if (outOfBounds(date)) return false; 210 return !inUnselectables(date); 211 } 212 213 /** 214 * @param date 215 * @return 216 */ 217 private boolean inUnselectables(Date date) { 218 for (Date unselectable : unselectableDates) { 219 if (isSameDay(unselectable, date)) return true; 220 } 221 return false; 222 } 223 224 /** 225 * Returns a boolean indication whether the given date is below 226 * the lower or above the upper bound. 227 * 228 * @param date 229 * @return 230 */ 231 private boolean outOfBounds(Date date) { 232 if (belowLowerBound(date)) return true; 233 if (aboveUpperBound(date)) return true; 234 return false; 235 } 236 237 /** 238 * @param date 239 * @return 240 */ 241 private boolean aboveUpperBound(Date date) { 242 if (upperBound != null) { 243 return endOfDay(upperBound).before(date); 244 } 245 return false; 246 } 247 248 /** 249 * @param date 250 * @return 251 */ 252 private boolean belowLowerBound(Date date) { 253 if (lowerBound != null) { 254 return startOfDay(lowerBound).after(date); 255 } 256 return false; 257 } 258 259 260 /** 261 * {@inheritDoc} 262 */ 263 @Override 264 public void clearSelection() { 265 if (isSelectionEmpty()) return; 266 selectedDates.clear(); 267 fireValueChanged(EventType.SELECTION_CLEARED); 268 } 269 270 271 /** 272 * {@inheritDoc} 273 */ 274 @Override 275 public SortedSet<Date> getSelection() { 276 return new TreeSet<Date>(selectedDates); 277 } 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override 283 public boolean isSelected(Date date) { 284 Contract.asNotNull(date, "date must not be null"); 285 if (isSelectionEmpty()) return false; 286 return isSameDay(selectedDates.first(), date); 287 } 288 289 290 291 /** 292 * {@inheritDoc}<p> 293 * 294 * Implemented to return the date itself. 295 */ 296 @Override 297 public Date getNormalizedDate(Date date) { 298 return new Date(date.getTime()); 299 } 300 301 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public boolean isSelectionEmpty() { 307 return selectedDates.isEmpty(); 308 } 309 310 311 /** 312 * {@inheritDoc} 313 */ 314 @Override 315 public SortedSet<Date> getUnselectableDates() { 316 return new TreeSet<Date>(unselectableDates); 317 } 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override 323 public void setUnselectableDates(SortedSet<Date> unselectables) { 324 Contract.asNotNull(unselectables, "unselectable dates must not be null"); 325 this.unselectableDates.clear(); 326 for (Date unselectableDate : unselectables) { 327 removeSelectionInterval(unselectableDate, unselectableDate); 328 unselectableDates.add(unselectableDate); 329 } 330 fireValueChanged(EventType.UNSELECTED_DATES_CHANGED); 331 } 332 333 /** 334 * {@inheritDoc} 335 */ 336 @Override 337 public boolean isUnselectableDate(Date date) { 338 return !isSelectable(date); 339 } 340 341 342 343 344 345}