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 */ 028package edu.wisc.ssec.mcidasv.data.dateChooser; 029 030import java.awt.BorderLayout; 031import java.awt.Font; 032import java.awt.Insets; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.awt.event.KeyEvent; 036import java.beans.PropertyChangeEvent; 037import java.beans.PropertyChangeListener; 038import java.net.URL; 039import java.util.Calendar; 040import java.util.Date; 041import java.util.Locale; 042 043import javax.swing.ImageIcon; 044import javax.swing.JButton; 045import javax.swing.JFrame; 046import javax.swing.JPanel; 047import javax.swing.JPopupMenu; 048import javax.swing.MenuElement; 049import javax.swing.MenuSelectionManager; 050import javax.swing.SwingUtilities; 051import javax.swing.event.ChangeEvent; 052import javax.swing.event.ChangeListener; 053 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057/** 058 * A date chooser containing a date editor and a button, that makes a JCalendar 059 * visible for choosing a date. If no date editor is specified, a 060 * JTextFieldDateEditor is used as default. 061 * 062 * @author Kai Toedter 063 * @version $LastChangedRevision: 101 $ 064 * @version $LastChangedDate: 2006-06-04 14:42:29 +0200 (So, 04 Jun 2006) $ 065 */ 066 067public class JDateChooser extends JPanel implements ActionListener, 068 PropertyChangeListener { 069 070 private static final Logger logger = LoggerFactory.getLogger(JDateChooser.class); 071 072 private static final long serialVersionUID = -4306412745720670722L; 073 074 protected IDateEditor dateEditor; 075 076 protected JButton calendarButton; 077 078 protected JCalendar jcalendar; 079 080 protected JPopupMenu popup; 081 082 protected boolean isInitialized; 083 084 protected boolean dateSelected; 085 086 protected Date lastSelectedDate; 087 088 private ChangeListener changeListener; 089 090 /** 091 * Creates a new JDateChooser. By default, no date is set and the textfield 092 * is empty. 093 */ 094 public JDateChooser() { 095 this(null, null, null, null); 096 } 097 098 /** 099 * Creates a new JDateChooser with given IDateEditor. 100 * 101 * @param dateEditor 102 * the dateEditor to be used used to display the date. if null, a 103 * JTextFieldDateEditor is used. 104 */ 105 public JDateChooser(IDateEditor dateEditor) { 106 this(null, null, null, dateEditor); 107 } 108 109 /** 110 * Creates a new JDateChooser. 111 * 112 * @param date 113 * the date or null 114 */ 115 public JDateChooser(Date date) { 116 this(date, null); 117 } 118 119 /** 120 * Creates a new JDateChooser. 121 * 122 * @param date 123 * the date or null 124 * @param dateFormatString 125 * the date format string or null (then MEDIUM SimpleDateFormat 126 * format is used) 127 */ 128 public JDateChooser(Date date, String dateFormatString) { 129 this(date, dateFormatString, null); 130 } 131 132 /** 133 * Creates a new JDateChooser. 134 * 135 * @param date 136 * the date or null 137 * @param dateFormatString 138 * the date format string or null (then MEDIUM SimpleDateFormat 139 * format is used) 140 * @param dateEditor 141 * the dateEditor to be used used to display the date. if null, a 142 * JTextFieldDateEditor is used. 143 */ 144 public JDateChooser(Date date, String dateFormatString, 145 IDateEditor dateEditor) { 146 this(null, date, dateFormatString, dateEditor); 147 } 148 149 /** 150 * Creates a new JDateChooser. If the JDateChooser is created with this 151 * constructor, the mask will be always visible in the date editor. Please 152 * note that the date pattern and the mask will not be changed if the locale 153 * of the JDateChooser is changed. 154 * 155 * @param datePattern 156 * the date pattern, e.g. "MM/dd/yy" 157 * @param maskPattern 158 * the mask pattern, e.g. "##/##/##" 159 * @param placeholder 160 * the placeholer charachter, e.g. '_' 161 */ 162 public JDateChooser(String datePattern, String maskPattern, char placeholder) { 163 this(null, null, datePattern, new JTextFieldDateEditor(datePattern, 164 maskPattern, placeholder)); 165 } 166 167 /** 168 * Creates a new JDateChooser. 169 * 170 * @param jcal 171 * the JCalendar to be used 172 * @param date 173 * the date or null 174 * @param dateFormatString 175 * the date format string or null (then MEDIUM Date format is 176 * used) 177 * @param dateEditor 178 * the dateEditor to be used used to display the date. if null, a 179 * JTextFieldDateEditor is used. 180 */ 181 public JDateChooser(JCalendar jcal, Date date, String dateFormatString, 182 IDateEditor dateEditor) { 183 setName("JDateChooser"); 184 185 this.dateEditor = dateEditor; 186 if (this.dateEditor == null) { 187 this.dateEditor = new JTextFieldDateEditor(); 188 } 189 this.dateEditor.addPropertyChangeListener("date", this); 190 191 if (jcal == null) { 192 jcalendar = new JCalendar(date); 193 } else { 194 jcalendar = jcal; 195 if (date != null) { 196 jcalendar.setDate(date); 197 } 198 } 199 200 setLayout(new BorderLayout()); 201 202 jcalendar.getDayChooser().addPropertyChangeListener("day", this); 203 204 // always fire"day" property even if the user selects 205 // the already selected day again 206 // jcalendar.getDayChooser().setAlwaysFireDayProperty(true); 207 208 // TJJ Jun 2015 - no, this was causing infinite loops of prop events 209 // between the two date pickers 210 jcalendar.getDayChooser().setAlwaysFireDayProperty(false); 211 212 setDateFormatString(dateFormatString); 213 setDate(date); 214 215 // Display a calendar button with an icon 216 URL iconURL = getClass().getResource( 217 "/com/toedter/calendar/images/JDateChooserIcon.gif"); 218 ImageIcon icon = new ImageIcon(iconURL); 219 220 calendarButton = new JButton(icon) { 221 private static final long serialVersionUID = -1913767779079949668L; 222 223 public boolean isFocusable() { 224 return false; 225 } 226 }; 227 calendarButton.setMargin(new Insets(0, 0, 0, 0)); 228 calendarButton.addActionListener(this); 229 230 // Alt + 'C' selects the calendar. 231 calendarButton.setMnemonic(KeyEvent.VK_C); 232 233 add(calendarButton, BorderLayout.EAST); 234 add(this.dateEditor.getUiComponent(), BorderLayout.CENTER); 235 236 calendarButton.setMargin(new Insets(0, 0, 0, 0)); 237 // calendarButton.addFocusListener(this); 238 239 popup = new JPopupMenu() { 240 private static final long serialVersionUID = -6078272560337577761L; 241 242 public void setVisible(boolean b) { 243 Boolean isCanceled = (Boolean) getClientProperty("JPopupMenu.firePopupMenuCanceled"); 244 if (b 245 || (!b && dateSelected) 246 || ((isCanceled != null) && !b && isCanceled 247 .booleanValue())) { 248 super.setVisible(b); 249 } 250 } 251 }; 252 253 popup.setLightWeightPopupEnabled(true); 254 255 popup.add(jcalendar); 256 257 lastSelectedDate = date; 258 259 // Corrects a problem that occured when the JMonthChooser's combobox is 260 // displayed, and a click outside the popup does not close it. 261 262 // The following idea was originally provided by forum user 263 // podiatanapraia: 264 changeListener = new ChangeListener() { 265 boolean hasListened = false; 266 267 public void stateChanged(ChangeEvent e) { 268 if (hasListened) { 269 hasListened = false; 270 return; 271 } 272 if (popup.isVisible() 273 && JDateChooser.this.jcalendar.monthChooser 274 .getComboBox().hasFocus()) { 275 MenuElement[] me = MenuSelectionManager.defaultManager() 276 .getSelectedPath(); 277 MenuElement[] newMe = new MenuElement[me.length + 1]; 278 newMe[0] = popup; 279 for (int i = 0; i < me.length; i++) { 280 newMe[i + 1] = me[i]; 281 } 282 hasListened = true; 283 MenuSelectionManager.defaultManager() 284 .setSelectedPath(newMe); 285 } 286 } 287 }; 288 MenuSelectionManager.defaultManager().addChangeListener(changeListener); 289 // end of code provided by forum user podiatanapraia 290 291 isInitialized = true; 292 } 293 294 /** 295 * Called when the jalendar button was pressed. 296 * 297 * @param e 298 * the action event 299 */ 300 public void actionPerformed(ActionEvent e) { 301 int x = calendarButton.getWidth() 302 - (int) popup.getPreferredSize().getWidth(); 303 int y = calendarButton.getY() + calendarButton.getHeight(); 304 305 Calendar calendar = Calendar.getInstance(); 306 Date date = dateEditor.getDate(); 307 if (date != null) { 308 calendar.setTime(date); 309 } 310 jcalendar.setCalendar(calendar); 311 popup.show(calendarButton, x, y); 312 dateSelected = false; 313 } 314 315 /** 316 * Listens for a "date" property change or a "day" property change event 317 * from the JCalendar. Updates the date editor and closes the popup. 318 * 319 * @param evt 320 * the event 321 */ 322 public void propertyChange(PropertyChangeEvent evt) { 323 324 if (evt.getPropertyName().equals("day")) { 325 if (popup.isVisible()) { 326 dateSelected = true; 327 popup.setVisible(false); 328 setDate(jcalendar.getCalendar().getTime()); 329 } 330 } else if (evt.getPropertyName().equals("date")) { 331 if (evt.getSource() == dateEditor) { 332 firePropertyChange("date", evt.getOldValue(), evt.getNewValue()); 333 } else { 334 setDate((Date) evt.getNewValue()); 335 } 336 } 337 338 // if event source was a day chooser, may need to check start/end day bounds 339 if (evt.getSource() instanceof JDayChooser) { 340 341 JDayChooser jdcSrc = (JDayChooser) evt.getSource(); 342 343 String srcName = jdcSrc.getName(); 344 String dstName = jcalendar.getDayChooser().getName(); 345 if ((srcName != null) && (dstName != null)) { 346 347 // property event from start day chooser to end day chooser 348 if (srcName.equals(JDayChooser.BEG_DAY) && (dstName.equals(JDayChooser.END_DAY))) { 349 JDayChooser jdcDst = jcalendar.getDayChooser(); 350 Calendar srcCal = Calendar.getInstance(); 351 srcCal.set(Calendar.YEAR, jdcSrc.getYear()); 352 srcCal.set(Calendar.MONTH, jdcSrc.getMonth()); 353 srcCal.set(Calendar.DAY_OF_MONTH, jdcSrc.getDay()); 354 Calendar dstCal = Calendar.getInstance(); 355 dstCal.set(Calendar.YEAR, jdcDst.getYear()); 356 dstCal.set(Calendar.MONTH, jdcDst.getMonth()); 357 dstCal.set(Calendar.DAY_OF_MONTH, jdcDst.getDay()); 358 if (srcCal.after(dstCal)) { 359 logger.debug("Adjusting: Src date exceeds Dst date..."); 360 jdcDst.setDay(srcCal.get(Calendar.DAY_OF_MONTH)); 361 jdcDst.setMonth(srcCal.get(Calendar.MONTH)); 362 jdcDst.setYear(srcCal.get(Calendar.YEAR)); 363 dateEditor.setDate(srcCal.getTime()); 364 if (getParent() != null) { 365 getParent().invalidate(); 366 } 367 } 368 } 369 370 // property event from end day chooser to start day chooser 371 if (srcName.equals(JDayChooser.END_DAY) && (dstName.equals(JDayChooser.BEG_DAY))) { 372 JDayChooser jdcDst = jcalendar.getDayChooser(); 373 Calendar srcCal = Calendar.getInstance(); 374 srcCal.set(Calendar.YEAR, jdcSrc.getYear()); 375 srcCal.set(Calendar.MONTH, jdcSrc.getMonth()); 376 srcCal.set(Calendar.DAY_OF_MONTH, jdcSrc.getDay()); 377 Calendar dstCal = Calendar.getInstance(); 378 dstCal.set(Calendar.YEAR, jdcDst.getYear()); 379 dstCal.set(Calendar.MONTH, jdcDst.getMonth()); 380 dstCal.set(Calendar.DAY_OF_MONTH, jdcDst.getDay()); 381 if (srcCal.before(dstCal)) { 382 logger.debug("Adjusting: End date preceeds Src date..."); 383 jdcDst.setDay(srcCal.get(Calendar.DAY_OF_MONTH)); 384 jdcDst.setMonth(srcCal.get(Calendar.MONTH)); 385 jdcDst.setYear(srcCal.get(Calendar.YEAR)); 386 dateEditor.setDate(srcCal.getTime()); 387 if (getParent() != null) { 388 getParent().invalidate(); 389 } 390 } 391 } 392 393 } 394 } 395 396 } 397 398 /** 399 * Updates the UI of itself and the popup. 400 */ 401 public void updateUI() { 402 super.updateUI(); 403 setEnabled(isEnabled()); 404 405 if (jcalendar != null) { 406 SwingUtilities.updateComponentTreeUI(popup); 407 } 408 } 409 410 /** 411 * Sets the locale. 412 * 413 * @param l 414 * The new locale value 415 */ 416 public void setLocale(Locale l) { 417 super.setLocale(l); 418 dateEditor.setLocale(l); 419 jcalendar.setLocale(l); 420 } 421 422 /** 423 * @return the jcalendar 424 */ 425 public JCalendar getJcalendar() { 426 return jcalendar; 427 } 428 429 /** 430 * Gets the date format string. 431 * 432 * @return Returns the dateFormatString. 433 */ 434 public String getDateFormatString() { 435 return dateEditor.getDateFormatString(); 436 } 437 438 /** 439 * Sets the date format string. E.g "MMMMM d, yyyy" will result in "July 21, 440 * 2004" if this is the selected date and locale is English. 441 * 442 * @param dfString 443 * The dateFormatString to set. 444 */ 445 public void setDateFormatString(String dfString) { 446 dateEditor.setDateFormatString(dfString); 447 invalidate(); 448 } 449 450 /** 451 * Returns the date. If the JDateChooser is started with a null date and no 452 * date was set by the user, null is returned. 453 * 454 * @return the current date 455 */ 456 public Date getDate() { 457 return dateEditor.getDate(); 458 } 459 460 /** 461 * Sets the date. Fires the property change "date" if date != null. 462 * 463 * @param date 464 * the new date. 465 */ 466 public void setDate(Date date) { 467 dateEditor.setDate(date); 468 if (getParent() != null) { 469 getParent().invalidate(); 470 } 471 } 472 473 /** 474 * Returns the calendar. If the JDateChooser is started with a null date (or 475 * null calendar) and no date was set by the user, null is returned. 476 * 477 * @return the current calendar 478 */ 479 public Calendar getCalendar() { 480 Date date = getDate(); 481 if (date == null) { 482 return null; 483 } 484 Calendar calendar = Calendar.getInstance(); 485 calendar.setTime(date); 486 return calendar; 487 } 488 489 /** 490 * Sets the calendar. Value null will set the null date on the date editor. 491 * 492 * @param calendar 493 * the calendar. 494 */ 495 public void setCalendar(Calendar calendar) { 496 if (calendar == null) { 497 dateEditor.setDate(null); 498 } else { 499 dateEditor.setDate(calendar.getTime()); 500 } 501 } 502 503 /** 504 * Enable or disable the JDateChooser. 505 * 506 * @param enabled 507 * the new enabled value 508 */ 509 public void setEnabled(boolean enabled) { 510 super.setEnabled(enabled); 511 if (dateEditor != null) { 512 dateEditor.setEnabled(enabled); 513 calendarButton.setEnabled(enabled); 514 } 515 } 516 517 /** 518 * Returns true, if enabled. 519 * 520 * @return true, if enabled. 521 */ 522 public boolean isEnabled() { 523 return super.isEnabled(); 524 } 525 526 /** 527 * Sets the icon of the buuton. 528 * 529 * @param icon 530 * The new icon 531 */ 532 public void setIcon(ImageIcon icon) { 533 calendarButton.setIcon(icon); 534 } 535 536 /** 537 * Sets the font of all subcomponents. 538 * 539 * @param font 540 * the new font 541 */ 542 public void setFont(Font font) { 543 if (isInitialized) { 544 dateEditor.getUiComponent().setFont(font); 545 jcalendar.setFont(font); 546 } 547 super.setFont(font); 548 } 549 550 /** 551 * Returns the JCalendar component. THis is usefull if you want to set some 552 * properties. 553 * 554 * @return the JCalendar 555 */ 556 public JCalendar getJCalendar() { 557 return jcalendar; 558 } 559 560 /** 561 * Returns the calendar button. 562 * 563 * @return the calendar button 564 */ 565 public JButton getCalendarButton() { 566 return calendarButton; 567 } 568 569 /** 570 * Returns the date editor. 571 * 572 * @return the date editor 573 */ 574 public IDateEditor getDateEditor() { 575 return dateEditor; 576 } 577 578 /** 579 * Sets a valid date range for selectable dates. If max is before min, the 580 * default range with no limitation is set. 581 * 582 * @param min 583 * the minimum selectable date or null (then the minimum date is 584 * set to 01\01\0001) 585 * @param max 586 * the maximum selectable date or null (then the maximum date is 587 * set to 01\01\9999) 588 */ 589 public void setSelectableDateRange(Date min, Date max) { 590 jcalendar.setSelectableDateRange(min, max); 591 dateEditor.setSelectableDateRange(jcalendar.getMinSelectableDate(), 592 jcalendar.getMaxSelectableDate()); 593 } 594 595 public void setMaxSelectableDate(Date max) { 596 jcalendar.setMaxSelectableDate(max); 597 dateEditor.setMaxSelectableDate(max); 598 } 599 600 public void setMinSelectableDate(Date min) { 601 jcalendar.setMinSelectableDate(min); 602 dateEditor.setMinSelectableDate(min); 603 } 604 605 /** 606 * Gets the maximum selectable date. 607 * 608 * @return the maximum selectable date 609 */ 610 public Date getMaxSelectableDate() { 611 return jcalendar.getMaxSelectableDate(); 612 } 613 614 /** 615 * Gets the minimum selectable date. 616 * 617 * @return the minimum selectable date 618 */ 619 public Date getMinSelectableDate() { 620 return jcalendar.getMinSelectableDate(); 621 } 622 623 /** 624 * Should only be invoked if the JDateChooser is not used anymore. Due to popup 625 * handling it had to register a change listener to the default menu 626 * selection manager which will be unregistered here. Use this method to 627 * cleanup possible memory leaks. 628 */ 629 public void cleanup() { 630 MenuSelectionManager.defaultManager().removeChangeListener(changeListener); 631 changeListener = null; 632 } 633 634 /** 635 * Creates a JFrame with a JDateChooser inside and can be used for testing. 636 * 637 * @param s 638 * The command line arguments 639 */ 640 public static void main(String[] s) { 641 JFrame frame = new JFrame("JDateChooser"); 642 JDateChooser dateChooser = new JDateChooser(); 643 // JDateChooser dateChooser = new JDateChooser(null, new Date(), null, 644 // null); 645 // dateChooser.setLocale(new Locale("de")); 646 // dateChooser.setDateFormatString("dd. MMMM yyyy"); 647 648 // dateChooser.setPreferredSize(new Dimension(130, 20)); 649 // dateChooser.setFont(new Font("Verdana", Font.PLAIN, 10)); 650 // dateChooser.setDateFormatString("yyyy-MM-dd HH:mm"); 651 652 // URL iconURL = dateChooser.getClass().getResource( 653 // "/com/toedter/calendar/images/JMonthChooserColor32.gif"); 654 // ImageIcon icon = new ImageIcon(iconURL); 655 // dateChooser.setIcon(icon); 656 657 frame.getContentPane().add(dateChooser); 658 frame.pack(); 659 frame.setVisible(true); 660 } 661 662}