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