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 }