001/* 002 * $Id: DaySelectionModel.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.ArrayList; 024import java.util.Calendar; 025import java.util.Date; 026import java.util.Locale; 027import java.util.SortedSet; 028import java.util.TreeSet; 029 030import org.jdesktop.swingx.event.EventListenerMap; 031import org.jdesktop.swingx.event.DateSelectionEvent.EventType; 032import org.jdesktop.swingx.util.Contract; 033 034/** 035 * 036 * DaySelectionModel is a (temporary?) implementation of DateSelectionModel 037 * which normalizes all dates to the start of the day, that is zeroes all 038 * time fields. Responsibility extracted from JXMonthView (which must 039 * respect rules of model instead of trying to be clever itself). 040 * 041 * @author Joshua Outwater 042 */ 043public class DaySelectionModel extends AbstractDateSelectionModel { 044 private SelectionMode selectionMode; 045 private SortedSet<Date> selectedDates; 046 private SortedSet<Date> unselectableDates; 047 048 /** 049 * 050 */ 051 public DaySelectionModel() { 052 this(null); 053 } 054 055 /** 056 * 057 */ 058 public DaySelectionModel(Locale locale) { 059 super(locale); 060 this.listenerMap = new EventListenerMap(); 061 this.selectionMode = SelectionMode.SINGLE_SELECTION; 062 this.selectedDates = new TreeSet<Date>(); 063 this.unselectableDates = new TreeSet<Date>(); 064 065 } 066 /** 067 * 068 */ 069 @Override 070 public SelectionMode getSelectionMode() { 071 return selectionMode; 072 } 073 074 /** 075 * 076 */ 077 @Override 078 public void setSelectionMode(final SelectionMode selectionMode) { 079 this.selectionMode = selectionMode; 080 clearSelection(); 081 } 082 083 //---------------------- selection ops 084 /** 085 * {@inheritDoc} 086 */ 087 @Override 088 public void addSelectionInterval(Date startDate, Date endDate) { 089 if (startDate.after(endDate)) { 090 return; 091 } 092 startDate = startOfDay(startDate); 093 endDate = startOfDay(endDate); 094 boolean added = false; 095 switch (selectionMode) { 096 case SINGLE_SELECTION: 097 if (isSelected(startDate)) return; 098 clearSelectionImpl(); 099 added = addSelectionImpl(startDate, startDate); 100 break; 101 case SINGLE_INTERVAL_SELECTION: 102 if (isIntervalSelected(startDate, endDate)) return; 103 clearSelectionImpl(); 104 added = addSelectionImpl(startDate, endDate); 105 break; 106 case MULTIPLE_INTERVAL_SELECTION: 107 if (isIntervalSelected(startDate, endDate)) return; 108 added = addSelectionImpl(startDate, endDate); 109 break; 110 default: 111 break; 112 } 113 if (added) { 114 fireValueChanged(EventType.DATES_ADDED); 115 } 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 @Override 122 public void setSelectionInterval(Date startDate, Date endDate) { 123 startDate = startOfDay(startDate); 124 endDate = startOfDay(endDate); 125 if (SelectionMode.SINGLE_SELECTION.equals(selectionMode)) { 126 if (isSelected(startDate)) return; 127 endDate = startDate; 128 } else { 129 if (isIntervalSelected(startDate, endDate)) return; 130 } 131 clearSelectionImpl(); 132 if (addSelectionImpl(startDate, endDate)) { 133 fireValueChanged(EventType.DATES_SET); 134 } 135 } 136 137 /** 138 * Checks and returns if the single date interval bounded by startDate and endDate 139 * is selected. This is useful only for SingleInterval mode. 140 * 141 * @param startDate the start of the interval 142 * @param endDate the end of the interval, must be >= startDate 143 * @return true the interval is selected, false otherwise. 144 */ 145 private boolean isIntervalSelected(Date startDate, Date endDate) { 146 if (isSelectionEmpty()) return false; 147 startDate = startOfDay(startDate); 148 endDate = startOfDay(endDate); 149 return selectedDates.first().equals(startDate) 150 && selectedDates.last().equals(endDate); 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public void removeSelectionInterval(Date startDate, Date endDate) { 158 if (startDate.after(endDate)) { 159 return; 160 } 161 162 startDate = startOfDay(startDate); 163 endDate = startOfDay(endDate); 164 long startDateMs = startDate.getTime(); 165 long endDateMs = endDate.getTime(); 166 ArrayList<Date> datesToRemove = new ArrayList<Date>(); 167 for (Date selectedDate : selectedDates) { 168 long selectedDateMs = selectedDate.getTime(); 169 if (selectedDateMs >= startDateMs && selectedDateMs <= endDateMs) { 170 datesToRemove.add(selectedDate); 171 } 172 } 173 174 if (!datesToRemove.isEmpty()) { 175 selectedDates.removeAll(datesToRemove); 176 fireValueChanged(EventType.DATES_REMOVED); 177 } 178 } 179 180 /** 181 * {@inheritDoc} 182 */ 183 @Override 184 public void clearSelection() { 185 if (isSelectionEmpty()) return; 186 clearSelectionImpl(); 187 fireValueChanged(EventType.SELECTION_CLEARED); 188 } 189 190 private void clearSelectionImpl() { 191 selectedDates.clear(); 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 public SortedSet<Date> getSelection() { 199 return new TreeSet<Date>(selectedDates); 200 } 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public Date getFirstSelectionDate() { 207 return isSelectionEmpty() ? null : selectedDates.first(); 208 } 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override 214 public Date getLastSelectionDate() { 215 return isSelectionEmpty() ? null : selectedDates.last(); 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public boolean isSelected(Date date) { 223 // JW: don't need Contract ... startOfDay will throw NPE if null 224 return selectedDates.contains(startOfDay(date)); 225 } 226 227 /** 228 * {@inheritDoc} 229 */ 230 @Override 231 public boolean isSelectionEmpty() { 232 return selectedDates.isEmpty(); 233 } 234 235 /** 236 * {@inheritDoc} 237 */ 238 @Override 239 public SortedSet<Date> getUnselectableDates() { 240 return new TreeSet<Date>(unselectableDates); 241 } 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override 247 public void setUnselectableDates(SortedSet<Date> unselectables) { 248 this.unselectableDates.clear(); 249 for (Date date : unselectables) { 250 unselectableDates.add(startOfDay(date)); 251 } 252 for (Date unselectableDate : this.unselectableDates) { 253 removeSelectionInterval(unselectableDate, unselectableDate); 254 } 255 fireValueChanged(EventType.UNSELECTED_DATES_CHANGED); 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override 262 public boolean isUnselectableDate(Date date) { 263 date = startOfDay(date); 264 return upperBound != null && upperBound.getTime() < date.getTime() || 265 lowerBound != null && lowerBound.getTime() > date.getTime() || 266 unselectableDates != null && unselectableDates.contains(date); 267 } 268 269 270 private boolean addSelectionImpl(final Date startDate, final Date endDate) { 271 boolean hasAdded = false; 272 calendar.setTime(startDate); 273 Date date = calendar.getTime(); 274 while (date.before(endDate) || date.equals(endDate)) { 275 if (!isUnselectableDate(date)) { 276 hasAdded = true; 277 selectedDates.add(date); 278 } 279 calendar.add(Calendar.DATE, 1); 280 date = calendar.getTime(); 281 } 282 return hasAdded; 283 } 284 285 286 /** 287 * {@inheritDoc} 288 */ 289 290 /** 291 * {@inheritDoc} <p> 292 * 293 * Implemented to return the start of the day which contains the date. 294 */ 295 @Override 296 public Date getNormalizedDate(Date date) { 297 Contract.asNotNull(date, "date must not be null"); 298 return startOfDay(date); 299 } 300 301}