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 }