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