001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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        protected void setDate(Date date, boolean firePropertyChange) {
182                Date oldDate = this.date;
183                this.date = date;
184
185                if (date == null) {
186                        setText("");
187                } else {
188                        calendar.setTime(date);
189                        hours = calendar.get(Calendar.HOUR_OF_DAY);
190                        minutes = calendar.get(Calendar.MINUTE);
191                        seconds = calendar.get(Calendar.SECOND);
192                        millis = calendar.get(Calendar.MILLISECOND);
193
194                        String formattedDate = dateFormatter.format(date);
195                        try {
196                                logger.debug("Setting text: " + formattedDate);
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        public void setDateFormatString(String dateFormatString) {
218                if (ignoreDatePatternChange) {
219                        return;
220                }
221
222                try {
223                        dateFormatter.applyPattern(dateFormatString);
224                } catch (RuntimeException e) {
225                        dateFormatter = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM);
226                        dateFormatter.setLenient(false);
227                }
228                this.datePattern = dateFormatter.toPattern();
229                setToolTipText(this.datePattern);
230                setDate(date, false);
231        }
232
233        /*
234         * (non-Javadoc)
235         * 
236         * @see com.toedter.calendar.IDateEditor#getDateFormatString()
237         */
238        public String getDateFormatString() {
239                return datePattern;
240        }
241
242        /*
243         * (non-Javadoc)
244         * 
245         * @see com.toedter.calendar.IDateEditor#getUiComponent()
246         */
247        public JComponent getUiComponent() {
248                return this;
249        }
250
251        /**
252         * After any user input, the value of the textfield is proofed. Depending on
253         * being a valid date, the value is colored green or red.
254         * 
255         * @param event
256         *            the caret event
257         */
258        public void caretUpdate(CaretEvent event) {
259                String text = getText().trim();
260                String emptyMask = maskPattern.replace('#', placeholder);
261
262                if (text.length() == 0 || text.equals(emptyMask)) {
263                        setForeground(Color.BLACK);
264                        return;
265                }
266
267                try {
268                        Date date = dateFormatter.parse(getText());
269                        if (dateUtil.checkDate(date)) {
270                                setForeground(darkGreen);
271                        } else {
272                                setForeground(Color.RED);
273                        }
274                } catch (Exception e) {
275                        setForeground(Color.RED);
276                }
277        }
278
279        /*
280         * (non-Javadoc)
281         * 
282         * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
283         */
284        public void focusLost(FocusEvent focusEvent) {
285                checkText();
286        }
287
288        private void checkText() {
289                try {
290                        Date date = dateFormatter.parse(getText());
291                        setDate(date, true);
292                } catch (Exception e) {
293                        // ignore
294                }
295        }
296
297        /*
298         * (non-Javadoc)
299         * 
300         * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
301         */
302        public void focusGained(FocusEvent e) {
303        }
304
305        /*
306         * (non-Javadoc)
307         * 
308         * @see java.awt.Component#setLocale(java.util.Locale)
309         */
310        public void setLocale(Locale locale) {
311                if (locale == getLocale() || ignoreDatePatternChange) {
312                        return;
313                }
314
315                super.setLocale(locale);
316                dateFormatter = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
317                setToolTipText(dateFormatter.toPattern());
318
319                setDate(date, false);
320                doLayout();
321        }
322
323        /**
324         * Creates a mask from a date pattern. This is a very simple (and
325         * incomplete) implementation that works only with numbers. A date pattern
326         * of "MM/dd/yy" will result in the mask "##/##/##". Probably you want to
327         * override this method if it does not fit your needs.
328         * 
329         * @param datePattern
330         *            the date pattern
331         * @return the mask
332         */
333        
334        public String createMaskFromDatePattern(String datePattern) {
335                String symbols = "GyMdkHmsSEDFwWahKzZ";
336                String mask = "";
337                for (int i = 0; i < datePattern.length(); i++) {
338                        char ch = datePattern.charAt(i);
339                        boolean symbolFound = false;
340                        for (int n = 0; n < symbols.length(); n++) {
341                                if (symbols.charAt(n) == ch) {
342                                        mask += "#";
343                                        symbolFound = true;
344                                        break;
345                                }
346                        }
347                        if (!symbolFound) {
348                                mask += ch;
349                        }
350                }
351                return mask;
352        }
353
354        /**
355         * Returns true, if the mask is visible.
356         * 
357         * @return true, if the mask is visible
358         */
359        public boolean isMaskVisible() {
360                return isMaskVisible;
361        }
362
363        /**
364         * Sets the mask visible.
365         * 
366         * @param isMaskVisible
367         *            true, if the mask should be visible
368         */
369        public void setMaskVisible(boolean isMaskVisible) {
370                this.isMaskVisible = isMaskVisible;
371                if (isMaskVisible) {
372                        if (maskFormatter == null) {
373                                try {
374                                        maskFormatter = new MaskFormatter(createMaskFromDatePattern(datePattern));
375                                        maskFormatter.setPlaceholderCharacter(this.placeholder);
376                                        maskFormatter.install(this);
377                                } catch (ParseException e) {
378                                        e.printStackTrace();
379                                }
380                        }
381                }
382        }
383
384        /**
385         * Returns the preferred size. If a date pattern is set, it is the size the
386         * date pattern would take.
387         */
388        public Dimension getPreferredSize() {
389
390                Dimension ret = null;
391                if (datePattern != null) {
392                        ret =(new JTextField(datePattern)).getPreferredSize();
393                        //return new JTextField(datePattern).getPreferredSize();
394                } else {
395                        ret = super.getPreferredSize();
396                //return super.getPreferredSize();
397                }
398                ret.setSize(90, 19);
399                return ret;
400        }
401
402        /**
403         * Validates the typed date and sets it (only if it is valid).
404         */
405        public void actionPerformed(ActionEvent e) {
406                checkText();
407        }
408
409        /**
410         * Enables and disabled the component. It also fixes the background bug
411         * 4991597 and sets the background explicitly to a
412         * TextField.inactiveBackground.
413         */
414        
415        public void setEnabled(boolean b) {
416                super.setEnabled(b);
417                if (!b) {
418                        super.setBackground(UIManager.getColor("TextField.inactiveBackground"));
419                }
420        }
421
422        /*
423         * (non-Javadoc)
424         * 
425         * @see com.toedter.calendar.IDateEditor#getMaxSelectableDate()
426         */
427        public Date getMaxSelectableDate() {
428                return dateUtil.getMaxSelectableDate();
429        }
430
431        /*
432         * (non-Javadoc)
433         * 
434         * @see com.toedter.calendar.IDateEditor#getMinSelectableDate()
435         */
436        public Date getMinSelectableDate() {
437                return dateUtil.getMinSelectableDate();
438        }
439
440        /*
441         * (non-Javadoc)
442         * 
443         * @see com.toedter.calendar.IDateEditor#setMaxSelectableDate(java.util.Date)
444         */
445        public void setMaxSelectableDate(Date max) {
446                dateUtil.setMaxSelectableDate(max);
447                checkText();
448        }
449
450        /*
451         * (non-Javadoc)
452         * 
453         * @see com.toedter.calendar.IDateEditor#setMinSelectableDate(java.util.Date)
454         */
455        public void setMinSelectableDate(Date min) {
456                dateUtil.setMinSelectableDate(min);
457                checkText();
458        }
459
460        /*
461         * (non-Javadoc)
462         * 
463         * @see com.toedter.calendar.IDateEditor#setSelectableDateRange(java.util.Date,
464         *      java.util.Date)
465         */
466        public void setSelectableDateRange(Date min, Date max) {
467                dateUtil.setSelectableDateRange(min, max);
468                checkText();
469        }
470
471        /**
472         * Creates a JFrame with a JCalendar inside and can be used for testing.
473         * 
474         * @param s
475         *            The command line arguments
476         */
477        public static void main(String[] s) {
478                JFrame frame = new JFrame("JTextFieldDateEditor");
479                JTextFieldDateEditor jTextFieldDateEditor = new JTextFieldDateEditor();
480                jTextFieldDateEditor.setDate(new Date());
481                frame.getContentPane().add(jTextFieldDateEditor);
482                frame.pack();
483                frame.setVisible(true);
484        }
485}