001/*
002 * $Id: JTextFieldDateEditor.java,v 1.2 2011/03/24 16:06:33 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
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
031package edu.wisc.ssec.mcidasv.data.dateChooser;
032
033import java.awt.Color;
034import java.awt.Dimension;
035import java.awt.event.ActionEvent;
036import java.awt.event.ActionListener;
037import java.awt.event.FocusEvent;
038import java.awt.event.FocusListener;
039import java.text.DateFormat;
040import java.text.ParseException;
041import java.text.SimpleDateFormat;
042import java.util.Calendar;
043import java.util.Date;
044import java.util.Locale;
045
046import javax.swing.JComponent;
047import javax.swing.JFormattedTextField;
048import javax.swing.JFrame;
049import javax.swing.JTextField;
050import javax.swing.UIManager;
051import javax.swing.event.CaretEvent;
052import javax.swing.event.CaretListener;
053import 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 */
066public 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}