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