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