001    /*
002     * $Id: JXDatePicker.java,v 1.23 2006/05/14 02:36:13 dmouse Exp $
003     *
004     * Copyright 2004 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;
022    
023    import org.jdesktop.swingx.calendar.DateSpan;
024    import org.jdesktop.swingx.calendar.JXMonthView;
025    import org.jdesktop.swingx.painter.gradient.BasicGradientPainter;
026    import org.jdesktop.swingx.plaf.DatePickerUI;
027    import org.jdesktop.swingx.plaf.JXDatePickerAddon;
028    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
029    
030    import javax.swing.*;
031    import javax.swing.JFormattedTextField.AbstractFormatter;
032    import javax.swing.JFormattedTextField.AbstractFormatterFactory;
033    import javax.swing.text.DefaultFormatterFactory;
034    import java.awt.*;
035    import java.awt.event.ActionEvent;
036    import java.awt.event.ActionListener;
037    import java.text.DateFormat;
038    import java.text.MessageFormat;
039    import java.text.ParseException;
040    import java.text.SimpleDateFormat;
041    import java.util.Date;
042    import java.util.TimeZone;
043    
044    /**
045     * A component that combines a button, an editable field and a JXMonthView
046     * component.  The user can select a date from the calendar component, which
047     * appears when the button is pressed.  The selection from the calendar
048     * component will be displayed in editable field.  Values may also be modified
049     * manually by entering a date into the editable field using one of the
050     * supported date formats.
051     *
052     * @author Joshua Outwater
053     */
054    public class JXDatePicker extends JComponent {
055    
056        static {
057          LookAndFeelAddons.contribute(new JXDatePickerAddon());
058        }
059    
060        /**
061         * UI Class ID
062         */
063        public static final String uiClassID = "DatePickerUI";
064    
065        public static final String EDITOR = "editor";
066        public static final String MONTH_VIEW = "monthView";
067        public static final String DATE_IN_MILLIS ="dateInMillis";
068        public static final String LINK_PANEL = "linkPanel";
069    
070        /** The editable date field that displays the date */
071        private JFormattedTextField _dateField;
072    
073        /**
074         * Popup that displays the month view with controls for
075         * traversing/selecting dates.
076         */
077        private JPanel _linkPanel;
078        private long _linkDate;
079        private MessageFormat _linkFormat;
080        private JXMonthView _monthView;
081        private String _actionCommand = "selectionChanged";
082        private boolean editable = true;
083    
084        /**
085         * Create a new date picker using the current date as the initial
086         * selection and the default abstract formatter
087         * <code>JXDatePickerFormatter</code>.
088         *
089         * The date picker is configured with the default time zone and locale
090         *
091         * @see #setTimeZone
092         * @see #getTimeZone
093         */
094        public JXDatePicker() {
095            this(System.currentTimeMillis());
096        }
097    
098        /**
099         * Create a new date picker using the specified time as the initial
100         * selection and the default abstract formatter
101         * <code>JXDatePickerFormatter</code>.
102         *
103         * The date picker is configured with the default time zone and locale
104         *
105         * @param millis initial time in milliseconds
106         * @see #setTimeZone
107         * @see #getTimeZone
108         */
109        public JXDatePicker(long millis) {
110            _monthView = new JXMonthView();
111            _monthView.setTraversable(true);
112    
113            _linkFormat = new MessageFormat(UIManager.getString("JXDatePicker.linkFormat"));
114    
115            _linkDate = System.currentTimeMillis();
116            _linkPanel = new TodayPanel();
117            
118            updateUI();
119    
120            _dateField.setValue(new Date(millis));
121        }
122    
123        /**
124         * @inheritDoc
125         */
126        public DatePickerUI getUI() {
127            return (DatePickerUI)ui;
128        }
129    
130        /**
131         * Sets the L&F object that renders this component.
132         *
133         * @param ui
134         */
135        public void setUI(DatePickerUI ui) {
136            super.setUI(ui);
137        }
138    
139        /**
140         * Resets the UI property with the value from the current look and feel.
141         *
142         * @see UIManager#getUI
143         */
144        @Override
145        public void updateUI() {
146            setUI((DatePickerUI)UIManager.getUI(this));
147            invalidate();
148        }
149    
150        /**
151         * @inheritDoc
152         */
153        @Override
154        public String getUIClassID() {
155            return uiClassID;
156        }
157    
158        /**
159         * Replaces the currently installed formatter and factory used by the
160         * editor.  These string formats are defined by the
161         * <code>java.text.SimpleDateFormat</code> class.
162         *
163         * @param formats The string formats to use.
164         * @see java.text.SimpleDateFormat
165         */
166        public void setFormats(String[] formats) {
167            DateFormat[] dateFormats = new DateFormat[formats.length];
168            for (int counter = formats.length - 1; counter >= 0; counter--) {
169                dateFormats[counter] = new SimpleDateFormat(formats[counter]);
170            }
171            setFormats(dateFormats);
172        }
173    
174        /**
175         * Replaces the currently installed formatter and factory used by the
176         * editor.
177         *
178         * @param formats The date formats to use.
179         */
180        public void setFormats(DateFormat[] formats) {
181            _dateField.setFormatterFactory(new DefaultFormatterFactory(
182                                    new JXDatePickerFormatter(formats)));
183        }
184    
185        /**
186         * Returns an array of the formats used by the installed formatter
187         * if it is a subclass of <code>JXDatePickerFormatter<code>.
188         * <code>javax.swing.JFormattedTextField.AbstractFormatter</code>
189         * and <code>javax.swing.text.DefaultFormatter</code> do not have
190         * support for accessing the formats used.
191         *
192         * @return array of formats or null if unavailable.
193         */
194        public DateFormat[] getFormats() {
195            // Dig this out from the factory, if possible, otherwise return null.
196            AbstractFormatterFactory factory = _dateField.getFormatterFactory();
197            if (factory != null) {
198                AbstractFormatter formatter = factory.getFormatter(_dateField);
199                if (formatter instanceof JXDatePickerFormatter) {
200                    return ((JXDatePickerFormatter)formatter).getFormats();
201                }
202            }
203            return null;
204        }
205    
206        /**
207         * Set the currently selected date.
208         *
209         * @param date date
210         */
211        public void setDate(Date date) {
212            _dateField.setValue(date);
213        }
214    
215        /**
216         * Set the currently selected date.
217         *
218         * @param millis milliseconds
219         */
220        public void setDateInMillis(long millis) {
221            _dateField.setValue(new Date(millis));
222        }
223    
224        /**
225         * Returns the currently selected date.
226         *
227         * @return Date
228         */
229        public Date getDate() {
230            return (Date)_dateField.getValue();
231        }
232    
233        /**
234         * Returns the currently selected date in milliseconds.
235         *
236         * @return the date in milliseconds
237         */
238        public long getDateInMillis() {
239            return ((Date)_dateField.getValue()).getTime();
240        }
241    
242        /**
243         * Return the <code>JXMonthView</code> used in the popup to
244         * select dates from.
245         *
246         * @return the month view component
247         */
248        public JXMonthView getMonthView() {
249            return _monthView;
250        }
251    
252        /**
253         * Set the component to use the specified JXMonthView.  If the new JXMonthView
254         * is configured to a different time zone it will affect the time zone of this
255         * component.
256         *
257         * @param monthView month view comopnent
258         * @see #setTimeZone
259         * @see #getTimeZone
260         */
261        public void setMonthView(JXMonthView monthView) {
262            JXMonthView oldMonthView = _monthView;
263            _monthView = monthView;
264            firePropertyChange(MONTH_VIEW, oldMonthView, _monthView);
265        }
266    
267        /**
268         * Gets the time zone.  This is a convenience method which returns the time zone
269         * of the JXMonthView being used.
270         *
271         * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
272         */
273        public TimeZone getTimeZone() {
274            return _monthView.getTimeZone();
275        }
276    
277        /**
278         * Sets the time zone with the given time zone value.    This is a convenience
279         * method which returns the time zone of the JXMonthView being used.
280         *
281         * @param tz The <code>TimeZone</code>.
282         */
283        public void setTimeZone(TimeZone tz) {
284            _monthView.setTimeZone(tz);
285    
286        }
287    
288        public long getLinkDate() {
289            return _linkDate;
290        }
291    
292        /**
293         * Set the date the link will use and the string defining a MessageFormat
294         * to format the link.  If no valid date is in the editor when the popup
295         * is displayed the popup will focus on the month the linkDate is in.  Calling
296         * this method will replace the currently installed linkPanel and install
297         * a new one with the requested date and format.
298         *
299         * @param linkDate Date in milliseconds
300         * @param linkFormatString String used to format the link
301         * @see java.text.MessageFormat
302         */
303        public void setLinkDate(long linkDate, String linkFormatString) {
304            _linkDate = linkDate;
305            _linkFormat = new MessageFormat(linkFormatString);
306            setLinkPanel(new TodayPanel());
307        }
308        
309        /**
310         * Return the panel that is used at the bottom of the popup.  The default
311         * implementation shows a link that displays the current month.
312         *
313         * @return The currently installed link panel
314         */
315        public JPanel getLinkPanel() {
316            return _linkPanel;
317        }
318        
319        /**
320         * Set the panel that will be used at the bottom of the popup.
321         *
322         * @param linkPanel The new panel to install in the popup
323         */
324        public void setLinkPanel(JPanel linkPanel) {
325            JPanel oldLinkPanel = _linkPanel;
326            _linkPanel = linkPanel;
327            firePropertyChange(LINK_PANEL, oldLinkPanel, _linkPanel);
328        }
329        
330        /**
331         * Returns the formatted text field used to edit the date selection.
332         *
333         * @return the formatted text field
334         */
335        public JFormattedTextField getEditor() {
336            return _dateField;
337        }
338    
339        public void setEditor(JFormattedTextField editor) {
340            JFormattedTextField oldEditor = _dateField;
341            _dateField = editor;
342            firePropertyChange(EDITOR, oldEditor, _dateField);
343        }
344    
345        @Override
346        public void setComponentOrientation(ComponentOrientation orientation) {
347            super.setComponentOrientation(orientation);
348            _monthView.setComponentOrientation(orientation);
349        }
350    
351        /**
352         * Returns true if the current value being edited is valid.
353         *
354         * @return true if the current value being edited is valid.
355         */
356        public boolean isEditValid() {
357            return _dateField.isEditValid();
358        }
359    
360        /**
361         * Forces the current value to be taken from the AbstractFormatter and
362         * set as the current value. This has no effect if there is no current
363         * AbstractFormatter installed.
364         */
365        public void commitEdit() throws ParseException {
366            _dateField.commitEdit();
367        }
368    
369        public void setEditable(boolean value) {
370            boolean oldEditable = isEditable();
371            editable = value;
372            firePropertyChange("editable", oldEditable, editable);
373            if (editable != oldEditable) {
374                repaint();
375            }
376        }
377    
378        public boolean isEditable() {
379            return editable;
380        }
381    
382        /**
383         * Get the baseline for the specified component, or a value less
384         * than 0 if the baseline can not be determined.  The baseline is measured
385         * from the top of the component.
386         *
387         * @param width Width of the component to determine baseline for.
388         * @param height Height of the component to determine baseline for.
389         * @return baseline for the specified component
390         */
391        public int getBaseline(int width, int height) {
392            return ((DatePickerUI)ui).getBaseline(width, height);
393        }
394        /**
395         * Returns the string currently used to identiy fired ActionEvents.
396         *
397         * @return String The string used for identifying ActionEvents.
398         */
399        public String getActionCommand() {
400            return _actionCommand;
401        }
402    
403        /**
404         * Sets the string used to identify fired ActionEvents.
405         *
406         * @param actionCommand The string used for identifying ActionEvents.
407         */
408        public void setActionCommand(String actionCommand) {
409            _actionCommand = actionCommand;
410        }
411    
412        /**
413         * Adds an ActionListener.
414         * <p>
415         * The ActionListener will receive an ActionEvent when a selection has
416         * been made.
417         *
418         * @param l The ActionListener that is to be notified
419         */
420        public void addActionListener(ActionListener l) {
421            listenerList.add(ActionListener.class, l);
422        }
423    
424        /**
425         * Removes an ActionListener.
426         *
427         * @param l The action listener to remove.
428         */
429        public void removeActionListener(ActionListener l) {
430            listenerList.remove(ActionListener.class, l);
431        }
432    
433        /**
434         * Fires an ActionEvent to all listeners.
435         */
436        protected void fireActionPerformed() {
437            Object[] listeners = listenerList.getListenerList();
438            ActionEvent e = null;
439            for (int i = listeners.length - 2; i >= 0; i -=2) {
440                if (listeners[i] == ActionListener.class) {
441                    if (e == null) {
442                        e = new ActionEvent(JXDatePicker.this,
443                                ActionEvent.ACTION_PERFORMED,
444                                _actionCommand);
445                    }
446                    ((ActionListener)listeners[i + 1]).actionPerformed(e);
447                }
448            }
449        }
450    
451        public void postActionEvent() {
452            fireActionPerformed();
453        }
454    
455        private final class TodayPanel extends JXPanel {
456            TodayPanel() {
457                super(new FlowLayout());
458                setBackgroundPainter(new BasicGradientPainter(0, 0, new Color(238, 238, 238), 0, 1, Color.WHITE));
459                JXHyperlink todayLink = new JXHyperlink(new TodayAction());
460                Color textColor = new Color(16, 66, 104);
461                todayLink.setUnclickedColor(textColor);
462                todayLink.setClickedColor(textColor);
463                add(todayLink);
464            }
465            
466            @Override
467            protected void paintComponent(Graphics g) {
468                super.paintComponent(g);
469    
470                g.setColor(new Color(187, 187, 187));
471                g.drawLine(0, 0, getWidth(), 0);
472                g.setColor(new Color(221, 221, 221));
473                g.drawLine(0, 1, getWidth(), 1);
474            }
475            
476            private final class TodayAction extends AbstractAction {
477                TodayAction() {
478                    super(_linkFormat.format(new Object[] { new Date(_linkDate) }));
479                }
480                
481                public void actionPerformed(ActionEvent ae) {
482                    DateSpan span = new DateSpan(_linkDate, _linkDate);
483                    _monthView.ensureDateVisible(span.getStart());
484                }
485            }
486        }
487    }