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