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