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