001    /*
002     * $Id: JTextFieldDateEditor.java,v 1.3 2012/02/19 17:35:46 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.data.dateChooser;
032    
033    import java.awt.Color;
034    import java.awt.Dimension;
035    import java.awt.event.ActionEvent;
036    import java.awt.event.ActionListener;
037    import java.awt.event.FocusEvent;
038    import java.awt.event.FocusListener;
039    import java.text.DateFormat;
040    import java.text.ParseException;
041    import java.text.SimpleDateFormat;
042    import java.util.Calendar;
043    import java.util.Date;
044    import java.util.Locale;
045    
046    import javax.swing.JComponent;
047    import javax.swing.JFormattedTextField;
048    import javax.swing.JFrame;
049    import javax.swing.JTextField;
050    import javax.swing.UIManager;
051    import javax.swing.event.CaretEvent;
052    import javax.swing.event.CaretListener;
053    import javax.swing.text.MaskFormatter;
054    
055    /**
056     * JTextFieldDateEditor is the default editor used by JDateChooser. It is a
057     * formatted text field, that colores valid dates green/black and invalid dates
058     * red. The date format patten and mask can be set manually. If not set, the
059     * MEDIUM pattern of a SimpleDateFormat with regards to the actual locale is
060     * used.
061     * 
062     * @author Kai Toedter
063     * @version $LastChangedRevision: 97 $
064     * @version $LastChangedDate: 2006-05-24 17:30:41 +0200 (Mi, 24 Mai 2006) $
065     */
066    public class JTextFieldDateEditor extends JFormattedTextField implements IDateEditor,
067                    CaretListener, FocusListener, ActionListener {
068    
069            private static final long serialVersionUID = -8901842591101625304L;
070    
071            protected Date date;
072    
073            protected SimpleDateFormat dateFormatter;
074    
075            protected MaskFormatter maskFormatter;
076    
077            protected String datePattern;
078    
079            protected String maskPattern;
080    
081            protected char placeholder;
082    
083            protected Color darkGreen;
084    
085            protected DateUtil dateUtil;
086    
087            private boolean isMaskVisible;
088    
089            private boolean ignoreDatePatternChange;
090    
091            private int hours;
092    
093            private int minutes;
094    
095            private int seconds;
096    
097            private int millis;
098    
099            private Calendar calendar;
100    
101            public JTextFieldDateEditor() {
102                    this(false, null, null, ' ');
103            }
104    
105            public JTextFieldDateEditor(String datePattern, String maskPattern, char placeholder) {
106                    this(true, datePattern, maskPattern, placeholder);
107            }
108    
109            public JTextFieldDateEditor(boolean showMask, String datePattern, String maskPattern,
110                            char placeholder) {
111                    dateFormatter = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM);
112                    dateFormatter.setLenient(false);
113    
114                    setDateFormatString(datePattern);
115                    if (datePattern != null) {
116                            ignoreDatePatternChange = true;
117                    }
118    
119                    this.placeholder = placeholder;
120    
121                    if (maskPattern == null) {
122                            this.maskPattern = createMaskFromDatePattern(this.datePattern);
123                    } else {
124                            this.maskPattern = maskPattern;
125                    }
126    
127                    setToolTipText(this.datePattern);
128                    setMaskVisible(showMask);
129    
130                    addCaretListener(this);
131                    addFocusListener(this);
132                    addActionListener(this);
133                    darkGreen = new Color(0, 150, 0);
134    
135                    calendar = Calendar.getInstance();
136    
137                    dateUtil = new DateUtil();
138            }
139    
140            /*
141             * (non-Javadoc)
142             * 
143             * @see com.toedter.calendar.IDateEditor#getDate()
144             */
145            public Date getDate() {
146                    try {
147                            calendar.setTime(dateFormatter.parse(getText()));
148                            calendar.set(Calendar.HOUR_OF_DAY, hours);
149                            calendar.set(Calendar.MINUTE, minutes);
150                            calendar.set(Calendar.SECOND, seconds);
151                            calendar.set(Calendar.MILLISECOND, millis);
152                            date = calendar.getTime();
153                    } catch (ParseException e) {
154                            date = null;
155                    }
156                    return date;
157            }
158    
159            /*
160             * (non-Javadoc)
161             * 
162             * @see com.toedter.calendar.IDateEditor#setDate(java.util.Date)
163             */
164            public void setDate(Date date) {
165                    setDate(date, true);
166            }
167    
168            /**
169             * Sets the date.
170             * 
171             * @param date
172             *            the date
173             * @param firePropertyChange
174             *            true, if the date property should be fired.
175             */
176            protected void setDate(Date date, boolean firePropertyChange) {
177                    Date oldDate = this.date;
178                    this.date = date;
179    
180                    if (date == null) {
181                            setText("");
182                    } else {
183                            calendar.setTime(date);
184                            hours = calendar.get(Calendar.HOUR_OF_DAY);
185                            minutes = calendar.get(Calendar.MINUTE);
186                            seconds = calendar.get(Calendar.SECOND);
187                            millis = calendar.get(Calendar.MILLISECOND);
188    
189                            String formattedDate = dateFormatter.format(date);
190                            try {
191                                    setText(formattedDate);
192                            } catch (RuntimeException e) {
193                                    e.printStackTrace();
194                            }
195                    }
196                    if (date != null && dateUtil.checkDate(date)) {
197                            setForeground(Color.BLACK);
198    
199                    }
200    
201                    if (firePropertyChange) {
202                            firePropertyChange("date", oldDate, date);
203                    }
204            }
205    
206            /*
207             * (non-Javadoc)
208             * 
209             * @see com.toedter.calendar.IDateEditor#setDateFormatString(java.lang.String)
210             */
211            public void setDateFormatString(String dateFormatString) {
212                    if (ignoreDatePatternChange) {
213                            return;
214                    }
215    
216                    try {
217                            dateFormatter.applyPattern(dateFormatString);
218                    } catch (RuntimeException e) {
219                            dateFormatter = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM);
220                            dateFormatter.setLenient(false);
221                    }
222                    this.datePattern = dateFormatter.toPattern();
223                    setToolTipText(this.datePattern);
224                    setDate(date, false);
225            }
226    
227            /*
228             * (non-Javadoc)
229             * 
230             * @see com.toedter.calendar.IDateEditor#getDateFormatString()
231             */
232            public String getDateFormatString() {
233                    return datePattern;
234            }
235    
236            /*
237             * (non-Javadoc)
238             * 
239             * @see com.toedter.calendar.IDateEditor#getUiComponent()
240             */
241            public JComponent getUiComponent() {
242                    return this;
243            }
244    
245            /**
246             * After any user input, the value of the textfield is proofed. Depending on
247             * being a valid date, the value is colored green or red.
248             * 
249             * @param event
250             *            the caret event
251             */
252            public void caretUpdate(CaretEvent event) {
253                    String text = getText().trim();
254                    String emptyMask = maskPattern.replace('#', placeholder);
255    
256                    if (text.length() == 0 || text.equals(emptyMask)) {
257                            setForeground(Color.BLACK);
258                            return;
259                    }
260    
261                    try {
262                            Date date = dateFormatter.parse(getText());
263                            if (dateUtil.checkDate(date)) {
264                                    setForeground(darkGreen);
265                            } else {
266                                    setForeground(Color.RED);
267                            }
268                    } catch (Exception e) {
269                            setForeground(Color.RED);
270                    }
271            }
272    
273            /*
274             * (non-Javadoc)
275             * 
276             * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
277             */
278            public void focusLost(FocusEvent focusEvent) {
279                    checkText();
280            }
281    
282            private void checkText() {
283                    try {
284                            Date date = dateFormatter.parse(getText());
285                            setDate(date, true);
286                    } catch (Exception e) {
287                            // ignore
288                    }
289            }
290    
291            /*
292             * (non-Javadoc)
293             * 
294             * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
295             */
296            public void focusGained(FocusEvent e) {
297            }
298    
299            /*
300             * (non-Javadoc)
301             * 
302             * @see java.awt.Component#setLocale(java.util.Locale)
303             */
304            public void setLocale(Locale locale) {
305                    if (locale == getLocale() || ignoreDatePatternChange) {
306                            return;
307                    }
308    
309                    super.setLocale(locale);
310                    dateFormatter = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
311                    setToolTipText(dateFormatter.toPattern());
312    
313                    setDate(date, false);
314                    doLayout();
315            }
316    
317            /**
318             * Creates a mask from a date pattern. This is a very simple (and
319             * incomplete) implementation thet works only with numbers. A date pattern
320             * of "MM/dd/yy" will result in the mask "##/##/##". Probably you want to
321             * override this method if it does not fit your needs.
322             * 
323             * @param datePattern
324             *            the date pattern
325             * @return the mask
326             */
327            public String createMaskFromDatePattern(String datePattern) {
328                    String symbols = "GyMdkHmsSEDFwWahKzZ";
329                    String mask = "";
330                    for (int i = 0; i < datePattern.length(); i++) {
331                            char ch = datePattern.charAt(i);
332                            boolean symbolFound = false;
333                            for (int n = 0; n < symbols.length(); n++) {
334                                    if (symbols.charAt(n) == ch) {
335                                            mask += "#";
336                                            symbolFound = true;
337                                            break;
338                                    }
339                            }
340                            if (!symbolFound) {
341                                    mask += ch;
342                            }
343                    }
344                    return mask;
345            }
346    
347            /**
348             * Returns true, if the mask is visible.
349             * 
350             * @return true, if the mask is visible
351             */
352            public boolean isMaskVisible() {
353                    return isMaskVisible;
354            }
355    
356            /**
357             * Sets the mask visible.
358             * 
359             * @param isMaskVisible
360             *            true, if the mask should be visible
361             */
362            public void setMaskVisible(boolean isMaskVisible) {
363                    this.isMaskVisible = isMaskVisible;
364                    if (isMaskVisible) {
365                            if (maskFormatter == null) {
366                                    try {
367                                            maskFormatter = new MaskFormatter(createMaskFromDatePattern(datePattern));
368                                            maskFormatter.setPlaceholderCharacter(this.placeholder);
369                                            maskFormatter.install(this);
370                                    } catch (ParseException e) {
371                                            e.printStackTrace();
372                                    }
373                            }
374                    }
375            }
376    
377            /**
378             * Returns the preferred size. If a date pattern is set, it is the size the
379             * date pattern would take.
380             */
381            public Dimension getPreferredSize() {
382    
383                    Dimension ret = null;
384                    if (datePattern != null) {
385                            ret =(new JTextField(datePattern)).getPreferredSize();
386                            //return new JTextField(datePattern).getPreferredSize();
387                    } else {
388                            ret = super.getPreferredSize();
389                    //return super.getPreferredSize();
390                    }
391                    ret.setSize(90, 19);
392                    return ret;
393            }
394    
395            /**
396             * Validates the typed date and sets it (only if it is valid).
397             */
398            public void actionPerformed(ActionEvent e) {
399                    checkText();
400            }
401    
402            /**
403             * Enables and disabled the compoment. It also fixes the background bug
404             * 4991597 and sets the background explicitely to a
405             * TextField.inactiveBackground.
406             */
407            public void setEnabled(boolean b) {
408                    super.setEnabled(b);
409                    if (!b) {
410                            super.setBackground(UIManager.getColor("TextField.inactiveBackground"));
411                    }
412            }
413    
414            /*
415             * (non-Javadoc)
416             * 
417             * @see com.toedter.calendar.IDateEditor#getMaxSelectableDate()
418             */
419            public Date getMaxSelectableDate() {
420                    return dateUtil.getMaxSelectableDate();
421            }
422    
423            /*
424             * (non-Javadoc)
425             * 
426             * @see com.toedter.calendar.IDateEditor#getMinSelectableDate()
427             */
428            public Date getMinSelectableDate() {
429                    return dateUtil.getMinSelectableDate();
430            }
431    
432            /*
433             * (non-Javadoc)
434             * 
435             * @see com.toedter.calendar.IDateEditor#setMaxSelectableDate(java.util.Date)
436             */
437            public void setMaxSelectableDate(Date max) {
438                    dateUtil.setMaxSelectableDate(max);
439                    checkText();
440            }
441    
442            /*
443             * (non-Javadoc)
444             * 
445             * @see com.toedter.calendar.IDateEditor#setMinSelectableDate(java.util.Date)
446             */
447            public void setMinSelectableDate(Date min) {
448                    dateUtil.setMinSelectableDate(min);
449                    checkText();
450            }
451    
452            /*
453             * (non-Javadoc)
454             * 
455             * @see com.toedter.calendar.IDateEditor#setSelectableDateRange(java.util.Date,
456             *      java.util.Date)
457             */
458            public void setSelectableDateRange(Date min, Date max) {
459                    dateUtil.setSelectableDateRange(min, max);
460                    checkText();
461            }
462    
463            /**
464             * Creates a JFrame with a JCalendar inside and can be used for testing.
465             * 
466             * @param s
467             *            The command line arguments
468             */
469            public static void main(String[] s) {
470                    JFrame frame = new JFrame("JTextFieldDateEditor");
471                    JTextFieldDateEditor jTextFieldDateEditor = new JTextFieldDateEditor();
472                    jTextFieldDateEditor.setDate(new Date());
473                    frame.getContentPane().add(jTextFieldDateEditor);
474                    frame.pack();
475                    frame.setVisible(true);
476            }
477    }