001/*
002 * $Id: AbstractDateSelectionModel.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.Calendar;
024import java.util.Collections;
025import java.util.Date;
026import java.util.List;
027import java.util.Locale;
028import java.util.SortedSet;
029import java.util.TimeZone;
030import java.util.TreeSet;
031
032import org.jdesktop.swingx.event.DateSelectionEvent;
033import org.jdesktop.swingx.event.DateSelectionListener;
034import org.jdesktop.swingx.event.EventListenerMap;
035import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
036
037/**
038 * Abstract base implementation of DateSelectionModel. Implements
039 * notification, Calendar related properties and lower/upper bounds.
040 * 
041 * @author Jeanette Winzenburg
042 */
043public abstract class AbstractDateSelectionModel implements DateSelectionModel {
044    public static final SortedSet<Date> EMPTY_DATES = Collections.unmodifiableSortedSet(new TreeSet<Date>());
045    
046    protected EventListenerMap listenerMap;
047    protected boolean adjusting;
048    protected Calendar calendar;
049    protected Date upperBound;
050    protected Date lowerBound;
051
052    /** 
053     * the locale used by the calendar. <p>
054     * NOTE: need to keep separately as a Calendar has no getter.
055     */
056    protected Locale locale;
057
058    /**
059     * Instantiates a DateSelectionModel with default locale.
060     */
061    public AbstractDateSelectionModel() {
062        this(null);
063    }
064    
065    /**
066     * Instantiates a DateSelectionModel with the given locale. If the locale is
067     * null, the Locale's default is used.
068     * 
069     * PENDING JW: fall back to JComponent.getDefaultLocale instead? We use this
070     *   with components anyway?
071     * 
072     * @param locale the Locale to use with this model, defaults to Locale.default()
073     *    if null.
074     */
075    public AbstractDateSelectionModel(Locale locale) {
076        this.listenerMap = new EventListenerMap();
077        setLocale(locale);
078    }
079
080    /**
081     * {@inheritDoc}
082     */
083    @Override
084    public Calendar getCalendar() {
085        return (Calendar) calendar.clone();
086    }
087
088    /**
089     * {@inheritDoc}
090     */
091    @Override
092    public int getFirstDayOfWeek() {
093        return calendar.getFirstDayOfWeek();
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    @Override
100    public void setFirstDayOfWeek(final int firstDayOfWeek) {
101        if (firstDayOfWeek == getFirstDayOfWeek()) return;
102        calendar.setFirstDayOfWeek(firstDayOfWeek);
103        fireValueChanged(EventType.CALENDAR_CHANGED);
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    public int getMinimalDaysInFirstWeek() {
111        return calendar.getMinimalDaysInFirstWeek();
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    @Override
118    public void setMinimalDaysInFirstWeek(int minimalDays) {
119        if (minimalDays == getMinimalDaysInFirstWeek()) return;
120        calendar.setMinimalDaysInFirstWeek(minimalDays);
121        fireValueChanged(EventType.CALENDAR_CHANGED);
122    }
123
124    
125    /**
126     * {@inheritDoc}
127     */
128    @Override
129    public TimeZone getTimeZone() {
130        return calendar.getTimeZone();
131    }
132
133    /**
134     * {@inheritDoc}
135     */
136    @Override
137    public void setTimeZone(TimeZone timeZone) {
138        if (getTimeZone().equals(timeZone)) return;
139        TimeZone oldTimeZone = getTimeZone();
140        calendar.setTimeZone(timeZone);
141        adjustDatesToTimeZone(oldTimeZone);
142        fireValueChanged(EventType.CALENDAR_CHANGED);
143    }
144
145    /**
146     * Adjusts all stored dates to a new time zone.
147     * This method is called after the change had been made. <p>
148     * 
149     * This implementation resets all dates to null, clears everything. 
150     * Subclasses may override to really map to the new time zone.
151     *
152     * @param oldTimeZone the old time zone
153     * 
154     */
155    protected void adjustDatesToTimeZone(TimeZone oldTimeZone) {
156        clearSelection();
157        setLowerBound(null);
158        setUpperBound(null);
159        setUnselectableDates(EMPTY_DATES);
160    }
161    
162    /**
163     * {@inheritDoc}
164     */
165    @Override
166    public Locale getLocale() {
167        return locale;
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    @Override
174    public void setLocale(Locale locale) {
175        if (locale == null) {
176            locale = Locale.getDefault();
177        }
178        if (locale.equals(getLocale())) return;
179        this.locale = locale;
180        if (calendar != null) {
181            calendar = Calendar.getInstance(calendar.getTimeZone(), locale);
182        } else {
183            calendar = Calendar.getInstance(locale);
184        }
185        fireValueChanged(EventType.CALENDAR_CHANGED);
186    }
187
188//------------------- utility methods
189    
190    /**
191     * Returns the start of the day of the given date in this model's calendar.
192     * NOTE: the calendar is changed by this operation.
193     * 
194     * @param date the Date to get the start for.
195     * @return the Date representing the start of the day of the input date.
196     */
197    protected Date startOfDay(Date date) {
198        return CalendarUtils.startOfDay(calendar, date);
199    }
200
201    /**
202     * Returns the end of the day of the given date in this model's calendar.
203     * NOTE: the calendar is changed by this operation.
204     * 
205     * @param date the Date to get the start for.
206     * @return the Date representing the end of the day of the input date.
207     */
208    protected Date endOfDay(Date date) {
209        return CalendarUtils.endOfDay(calendar, date);
210    }
211
212    /**
213     * Returns a boolean indicating whether the given dates are on the same day in
214     * the coordinates of the model's calendar.
215     * 
216     * @param selected one of the dates to check, must not be null.
217     * @param compare the other of the dates to check, must not be null.
218     * @return true if both dates represent the same day in this model's calendar.
219     */
220    protected boolean isSameDay(Date selected, Date compare) {
221        return startOfDay(selected).equals(startOfDay(compare));
222    }
223
224//------------------- bounds
225
226    /**
227     * {@inheritDoc}
228     */
229    @Override
230    public Date getUpperBound() {
231        return upperBound;
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    @Override
238    public void setUpperBound(Date upperBound) {
239        if (upperBound != null) {
240            upperBound = getNormalizedDate(upperBound);
241        }
242        if (CalendarUtils.areEqual(upperBound, getUpperBound()))
243            return;
244        this.upperBound = upperBound;
245        if (this.upperBound != null && !isSelectionEmpty()) {
246            long justAboveUpperBoundMs = this.upperBound.getTime() + 1;
247            removeSelectionInterval(new Date(justAboveUpperBoundMs),
248                    getLastSelectionDate());
249        }
250        fireValueChanged(EventType.UPPER_BOUND_CHANGED);
251    }
252
253    /**
254     * {@inheritDoc}
255     */
256    @Override
257    public Date getLowerBound() {
258        return lowerBound;
259    }
260
261    /**
262     * {@inheritDoc}
263     */
264    @Override
265    public void setLowerBound(Date lowerBound) {
266        if (lowerBound != null) {
267            lowerBound = getNormalizedDate(lowerBound);
268        }
269        if (CalendarUtils.areEqual(lowerBound, getLowerBound()))
270            return;
271        this.lowerBound = lowerBound;
272        if (this.lowerBound != null && !isSelectionEmpty()) {
273            // Remove anything below the lower bound
274            long justBelowLowerBoundMs = this.lowerBound.getTime() - 1;
275            removeSelectionInterval(getFirstSelectionDate(), new Date(
276                    justBelowLowerBoundMs));
277        }
278        fireValueChanged(EventType.LOWER_BOUND_CHANGED);
279    }
280
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    public boolean isAdjusting() {
287        return adjusting;
288    }
289
290    /**
291     * {@inheritDoc}
292     */
293    @Override
294    public void setAdjusting(boolean adjusting) {
295        if (adjusting == isAdjusting()) return;
296        this.adjusting = adjusting;
297       fireValueChanged(adjusting ? EventType.ADJUSTING_STARTED : EventType.ADJUSTING_STOPPED);
298        
299    }
300
301//----------------- notification    
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public void addDateSelectionListener(DateSelectionListener l) {
307        listenerMap.add(DateSelectionListener.class, l);
308    }
309
310    /**
311     * {@inheritDoc}
312     */
313    @Override
314    public void removeDateSelectionListener(DateSelectionListener l) {
315        listenerMap.remove(DateSelectionListener.class, l);
316    }
317
318    public List<DateSelectionListener> getDateSelectionListeners() {
319        return listenerMap.getListeners(DateSelectionListener.class);
320    }
321
322    protected void fireValueChanged(DateSelectionEvent.EventType eventType) {
323        List<DateSelectionListener> listeners = getDateSelectionListeners();
324        DateSelectionEvent e = null;
325
326        for (DateSelectionListener listener : listeners) {
327            if (e == null) {
328                e = new DateSelectionEvent(this, eventType, isAdjusting());
329            }
330            listener.valueChanged(e);
331        }
332    }
333
334
335
336}